mantaraya36's blog

Brain dump

Monthly Archives: July 2011

Update on Schema properties

My original proposal has of course changed substantially! First, instead of defining a new data structure with multiple types, I’ve started using xmmsv_t which already exists and does most of the work (IPC interchange, value serializing, memory mangement). I only needed to add support for floats. Easy, right?

It turned out to be very complicated as there is no way to serialize float values in a portable manner, because locales would interfere (if you plan to use sprintf) and because you cannot guarantee how floats will be stored in memory on all platforms (especially those which are not IEEE 754 compliant!). So I ended up with a comprimise which is using frexp to separate the float values mantissa and exponent, and encode them as two integers. This appears to give on simple testing a presicion of about 6 or 7 significant figures, which should be sufficient for XMMS2 config values. This is now available at my git repo:

https://github.com/mantaraya36/xmms2-mantaraya36/tree/config-schemas

I’ve also started work on the config properties side, and have settled on the following API for the new system:

/*
 * New config properties using schemas
 */
 xmms_config_node_t *xmms_config_node_lookup (const gchar *path);
 xmms_config_node_t *xmms_config_node_create (const gchar *name, xmmsv_type_t type);
/* Register nodes in the property tree. Callbacks are ignored for lists and dicts */
 xmms_config_node_t *xmms_config_node_register (xmms_config_node_t *node, xmms_object_handler_t cb, gpointer userdata);
gboolean xmms_config_node_unregister (xmms_config_node_t *node);
/* check node types */
 gboolean xmms_config_node_is_type (xmms_config_node_t *node, xmmsv_type_t type);
/* node query operations. Will return the first element if node is a list or array*/
 gint32 xmms_config_node_get_int (xmms_config_node_t *node, gboolean *ok); /* ok can be NULL */
 gfloat xmms_config_node_get_float (xmms_config_node_t *node, gboolean *ok);
 const gchar *xmms_config_node_get_string (xmms_config_node_t *node, gboolean *ok);
 xmms_config_node_t *xmms_config_node_get_index_node (xmms_config_node_t *parent_node, gint index);
 xmms_config_node_t *xmms_config_node_get_key_node (xmms_config_node_t *parent_node, const gchar *name);
/* node value setters. will set the vaue for the first element if node is a list or array */
 void xmms_config_node_set_int (xmms_config_node_t *node, gint value, gboolean *ok);
 void xmms_config_node_set_float (xmms_config_node_t *node, gfloat value, gboolean *ok);
 void xmms_config_node_set_string (xmms_config_node_t *node, const gchar *value, gboolean *ok);
 /* for dicts only: */
 void xmms_config_node_set_from_hash (xmms_config_node_t *parent_node, GHashTable *table);
 /* for lists only: */
 void xmms_config_node_set_from_array (xmms_config_node_t *parent_node, GArray *list);
/* element operations (for lists and dicts) */
 gint xmms_config_node_element_count (xmms_config_node_t *node);
 void xmms_config_node_element_remove_index (xmms_config_node_t *parent_node, gint index, gboolean *ok);
 void xmms_config_node_element_remove_key (xmms_config_node_t *parent_node, xmms_config_node_t *node, gboolean *ok);
/* query node elements values */
 gint xmms_config_node_get_element_int (xmms_config_node_t *parent_node, gint index, gboolean *ok);
 gfloat xmms_config_node_get_element_float (xmms_config_node_t *parent_node, gint index, gboolean *ok);
 const gchar *xmms_config_node_get_element_string (xmms_config_node_t *parent_node, gint index, gboolean *ok);
/* set node elements values */
 void xmms_config_node_set_element_int (xmms_config_node_t *parent_node, gint index, gint value, gboolean *ok);
 void xmms_config_node_set_element_float (xmms_config_node_t *parent_node, gint index, gfloat value, gboolean *ok);
 void xmms_config_node_set_element_string (xmms_config_node_t *parent_node, gint index, const gchar* value, gboolean *ok);
/* list node operations */
 void xmms_config_node_resize (xmms_config_node_t *node, gint size, gboolean *ok);
 void xmms_config_node_append (xmms_config_node_t *parent_node, xmms_config_node_t *node, gboolean *ok);
 void xmms_config_node_insert (xmms_config_node_t *parent_node, gint index, xmms_config_node_t *node, gboolean *ok);
/* callbacks (can only be set for value nodes) */
 void xmms_config_node_callback_set (xmms_config_node_t *node, xmms_object_handler_t cb, gpointer userdata);
 void xmms_config_node_callback_remove (xmms_config_node_t *node, xmms_object_handler_t cb, gpointer userdata);

Nodes will hold a copy of the value in the tree and the node’s address,, so any operations on nodes can be made thread-safe.

Advertisements

Schema properties

OK, so here are my ideas to enhance the server config properties system. This is my initial proposal which will surely have changes after a round of feedback.

Current state

Currently properties are stored in a GTree structure in the file config.c, in a global variable from the following struct:

00085 struct xmms_config_St {
00086     xmms_object_t obj;
00087
00088     const gchar *filename;
00089     GTree *properties;
00090
00091     /* Lock on globals are great! */
00092     GMutex *mutex;
00093
00094     /* parsing */
00095     gboolean is_parsing;
00096     GQueue *states;
00097     GQueue *sections;
00098     gchar *value_name;
00099     guint version;
00100 };

The properties themselves are very simple, and basically contain a name and a string value:

00105 struct xmms_config_property_St {
00106     xmms_object_t obj;
00107
00108     /** Name of the config directive */
00109     const gchar *name;
00110     /** The data */
00111     gchar *value;
00112 };

Important considerations

  1. The new system must support (at least initially) the current one, to avoid breaking plugins. It’s likely that once the new system is in place, a simple script could be run to modify all code to use the new system, and the old methods removed or left with compiler deprecated warnings.
  2. The new system must support reading the xml config file, and from it produce the config tree for the new system. I’m thinking of breaking backwards compatibility of the xml file, so once you run a version with the new system, the properties will be written with a few additional elements which are not understood by the old parser.

Proposal

The new system will store the properties in a tree structure made up of linked nodes. I decided against using GNode or GVariant chains as they offer more functionality than needed which would make it easier to introduce bugs if you use the wrong way to put data in. Nodes will have :

struct xmms_config_node_St {
    xmms_object_t obj;
    const gchar *name;
    node_type_t type;
    node_data_t *data;

    struct xmms_config_node_St *parent_node;
}

The data for each node can be of type: CONFIG_LIST, CONFIG_ARRAY, CONFIG_INT, CONFIG_FLOAT, or CONFIG_STRING. The last three will be final nodes in the chain, where actual values are stored. These three types hold values directly in the data pointer as gint, gfloat or gchar*.

CONFIG_LIST nodes hold a GList whose elements are nodes. This should cover hierarchical properties, starting from the root node, like:

alsa.device = default
alsa.mixer = PCM
alsa.mixer_dev = default
alsa.mixer_index = 0

where the node called “alsa” has data of type CONFIG_LIST, and it holds a list with four nodes of type string,string, string and int. It is important to note that no to nodes in a list can have the same name.

The root node is of type CONFIG_LIST, and all config options are appended as new nodes to that list. The name of the root node is ignored.

CONFIG_ARRAY is similar to CONFIG_LIST, but it is designed to handle numbered properties, as in:

effect.order.0
effect.order.1

So the nodes that comprise CONFIG_ARRAYS, do not have names, as they take the name according to their order in the GList. Counting starts from 0. The main limitation this introduces is that numbered lists will not allow named elements, for example this would not be allowed:

effect.order.0
effect.order.1
effect.order.type

as the “order” node contains a numbered list, so “type” is not a valid name in this case.

Naturally, lists can be nested within lists or arrays and viceversa.

Interface

The interface to the new system will look like:

xmms_config_node_t *xmms_config_node_lookup (const gchar *path);
xmms_config_node_t *xmms_config_node_new (node_type_t type, const gchar *name = "");

/* frees a node and its children recursively */
void xmms_config_node_free (xmms_config_node_t *);

gboolean xmms_config_node_is_list (xmms_config_node_t *node);
gboolean xmms_config_node_is_array (xmms_config_node_t *node);

gboolean xmms_config_node_is_value (xmms_config_node_t *node);
gboolean xmms_config_node_is_int (xmms_config_node_t *node);
gboolean xmms_config_node_is_float (xmms_config_node_t *node);
gboolean xmms_config_node_is_string (xmms_config_node_t *node);

void xmms_config_node_get_path (xmms_config_node_t *node, gchar *path);

const gchar* xmms_config_node_get_name (xmms_config_node_t *node);
void xmms_config_node_set_name (xmms_config_node_t *node, const gchar *name);
/* callbacks can only be set for value nodes */
void xmms_config_node_callback_set (xmms_config_property_t *prop, xmms_object_handler_t cb, gpointer userdata);
void xmms_config_node_callback_remove (xmms_config_property_t *prop, xmms_object_handler_t cb, gpointer userdata);

/* value node operations. Will return the first element if node is a list or array*/
gint xmms_config_node_get_int (xmms_config_node_t *node, gboolean *ok = NULL);
gfloat xmms_config_node_get_float (xmms_config_node_t *node, gboolean *ok = NULL);
const gchar *xmms_config_node_get_string (xmms_config_node_t *node, gboolean *ok = NULL);

/* will set the vaue for the first element if node is a list or array */
void xmms_config_node_set_int (xmms_config_node_t *node, gint value, gboolean *ok = NULL);
void xmms_config_node_set_float (xmms_config_node_t *node, gfloat value, gboolean *ok = NULL);
void xmms_config_node_set_string (xmms_config_node_t *node, const gchar *value, gboolean *ok = NULL);

/* array and list node operations */
void xmms_config_node_resize (xmms_config_node_t *node, gint size, gboolean *ok = NULL);
void xmms_config_node_append (xmms_config_node_t *parent_node, xmms_config_node_t *node, gboolean *ok = NULL);
void xmms_config_node_insert (xmms_config_node_t *parent_node, gint index, xmms_config_node_t *node, gboolean *ok = NULL);
gint xmms_config_node_element_count (xmms_config_node_t *node);
void xmms_config_node_element_remove (xmms_config_node_t *parent_node, gint index, gboolean *ok = NULL);
void xmms_config_node_element_remove (xmms_config_node_t *parent_node, xmms_config_node_t *node, gboolean *ok = NULL);

xmms_config_node_t *xmms_config_node_get_element (xmms_config_node_t *parent_node, gint index);
xmms_config_node_t *xmms_config_node_get_element (xmms_config_node_t *parent_node, const gchar *name);

gint xmms_config_node_get_element_int (xmms_config_node_t *parent_node, gint index, gboolean *ok = NULL);
gfloat xmms_config_node_get_element_float (xmms_config_node_t *parent_node, gint index, gboolean *ok = NULL);
const gchar *xmms_config_node_get_element_string (xmms_config_node_t *parent_node, gint index, gboolean *ok = NULL);

void xmms_config_node_set_element_int (xmms_config_node_t *parent_node, gint index, gint value, gboolean *ok = NULL);
void xmms_config_node_set_element_float (xmms_config_node_t *parent_node, gint index, gfloat value, gboolean *ok = NULL);
void xmms_config_node_set_element_string (xmms_config_node_t *parent_node, gint index, const gchar* value, gboolean *ok = NULL);

void xmms_config_node_set_from_hash (xmms_config_node_t *parent_node, GHashTable table);

Command Line interaction

The command line client will be able to manipulate values in the usual way, so it only needs to be enhanced for array and list usage.

You will be able to set the elements for exisitng elements of a list or array from the command line by separating the values by commas. They values will be converted internally to the type for each node. So for example for the config properties:

alsa.device = default
alsa.mixer = PCM
alsa.mixer_dev = default
alsa.mixer_index = 0

You will be able to do:

nyxmms server config alsa default,PCM,default,0

The user can also dynamically resize lists and arrays. The command line user will be able to make lists longer, but not shorter (as the server might count on some properties it created being there). So for example, the user could set a chain of ladspa plugins like this:

nyxmms server config ladpsa.plugin amp.so,tap_reverb.so,tap_tubewarmth.so

and the ladspa plugin list will be resized to accomodate them if it can’t already.

Dynamic effect chains

The goal of this part of the project is to make changes in the effects chain have effect immediately, so if you add a ladspa plugin, or Eq to the chain, you won’t have to wait for the next song, when the chain is rebuilt, to hear it.

So, I’ve been looking at the code that builds the xform chain, the code that processes the chain, and the code that handles changes in effect chain config properties (currently only the effect.order.X properties exist).

Current behavior

Chains are created in the xmms_output_filler() function in output.c. This function runs a loop continuously filling the output buffer from the output of the xform chain, but it checks if the chain exists before doing this. If the chain doesn’t exist, it calls the xmms_xform_chain_setup() function from xform.c.This function returns the pointer to the last element of the chain. It calls xmms_xform_chain_setup_url() which finds the correct decoder for the current media, inserts a segment plugin (to keep track of time) after it and then builds the effects chain by calling add_effects(). This function needs to be passed as arguments the last xform, where the effects chain will be attached, the medialib entry url and the goal formats. Finally, the add_effects() function scans the effect.order.X config properties, intializing the plugins.

In the xform.c file, the update_effect_properties() function is the callback when effect chain properties change. This callback currently only checks whether the plugin exists, registers the “enabled” property for it and creates a new empty effect.order slot after the last one.

What needs to be done

My plan is that when a change in the effect.order.X config properties takes place (the update_effect_propeties() function is called), it will force a rebuild of the effects part of the xform chain. I toyed with the idea of just moving and inserting new items, but that would require more subtantial modifications to the xform basic structures, to make the chain more aware of its members and its state. This is something I think is unnecessary in this context, as rebuilding the chain should be fast enough for most situations and where there could be minor glitches in the audio, that could be something the user would expect and accept since he is adding or removing an effect.

What will happen is that whenever the config property is changed, the xform object will be marked as needing refresh. This will be picked up by the loop in xmms_output_filler and thelower part of the chain will be rebuilt.

The data passed to the update_effect_properties callback needs to be changed to include enough to do number 1 above and to mark that the chain needs remaking. Currently the callback data is used to pass only the number of effects in the chain, to be able to add an empty element at the end.

For each effect, their seek function must be called, in case they depend on the current song position and time. The value must be acquired from the segment plugin just before building the chain.