1 /*
2   LV2 State Map
3   Copyright 2016 David Robillard <d@drobilla.net>
4 
5   Permission to use, copy, modify, and/or distribute this software for any
6   purpose with or without fee is hereby granted, provided that the above
7   copyright notice and this permission notice appear in all copies.
8 
9   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 
18 #include "lv2/atom/atom.h"
19 #include "lv2/urid/urid.h"
20 
21 #include <stdarg.h>
22 #include <stdlib.h>
23 
24 /** Entry in an array that serves as a dictionary of properties. */
25 typedef struct {
26 	const char* uri;
27 	LV2_URID    urid;
28 	LV2_Atom*   value;
29 } StateMapItem;
30 
31 /** Comparator for StateMapItems sorted by URID. */
32 static int
state_map_cmp(const void * a,const void * b)33 state_map_cmp(const void* a, const void* b)
34 {
35 	const StateMapItem* ka = (const StateMapItem*)a;
36 	const StateMapItem* kb = (const StateMapItem*)b;
37 	if (ka->urid < kb->urid) {
38 		return -1;
39 	} else if (kb->urid < ka->urid) {
40 		return 1;
41 	}
42 	return 0;
43 }
44 
45 /** Helper macro for terse state map initialisation. */
46 #define STATE_MAP_INIT(type, ptr) \
47 	(LV2_ATOM__ ## type), \
48 	(sizeof(*ptr) - sizeof(LV2_Atom)), \
49 	(ptr)
50 
51 /**
52    Initialise a state map.
53 
54    The variable parameters list must be NULL terminated, and is a sequence of
55    const char* uri, const char* type, uint32_t size, LV2_Atom* value.  The
56    value must point to a valid atom that resides elsewhere, the state map is
57    only an index and does not contain actual state values.  The macro
58    STATE_MAP_INIT can be used to make simpler code when state is composed of
59    standard atom types, for example:
60 
61    struct Plugin {
62        LV2_URID_Map* map;
63        StateMapItem  props[3];
64        // ...
65    };
66 
67    state_map_init(
68        self->props, self->map, self->map->handle,
69        PLUG_URI "#gain",   STATE_MAP_INIT(Float,  &state->gain),
70        PLUG_URI "#offset", STATE_MAP_INIT(Int,    &state->offset),
71        PLUG_URI "#file",   STATE_MAP_INIT(Path,   &state->file),
72        NULL);
73 */
74 static void
state_map_init(StateMapItem dict[],LV2_URID_Map * map,LV2_URID_Map_Handle handle,...)75 state_map_init(StateMapItem        dict[],
76                LV2_URID_Map*       map,
77                LV2_URID_Map_Handle handle,
78                /* const char* uri, const char* type, uint32_t size, LV2_Atom* value */ ...)
79 {
80 	// Set dict entries from parameters
81 	unsigned i = 0;
82 	va_list  args;
83 	va_start(args, handle);
84 	for (const char* uri; (uri = va_arg(args, const char*)); ++i) {
85 		const char*     type  = va_arg(args, const char*);
86 		const uint32_t  size  = va_arg(args, uint32_t);
87 		LV2_Atom* const value = va_arg(args, LV2_Atom*);
88 		dict[i].uri         = uri;
89 		dict[i].urid        = map->map(map->handle, uri);
90 		dict[i].value       = value;
91 		dict[i].value->size = size;
92 		dict[i].value->type = map->map(map->handle, type);
93 	}
94 	va_end(args);
95 
96 	// Sort for fast lookup by URID by state_map_find()
97 	qsort(dict, i, sizeof(StateMapItem), state_map_cmp);
98 }
99 
100 /**
101    Retrieve an item from a state map by URID.
102 
103    This takes O(lg(n)) time, and is useful for implementing generic property
104    access with little code, for example to respond to patch:Get messages for a
105    specific property.
106 */
107 static StateMapItem*
state_map_find(StateMapItem dict[],uint32_t n_entries,LV2_URID urid)108 state_map_find(StateMapItem dict[], uint32_t n_entries, LV2_URID urid)
109 {
110 	const StateMapItem key = { NULL, urid, NULL };
111 	return (StateMapItem*)bsearch(
112 		&key, dict, n_entries, sizeof(StateMapItem), state_map_cmp);
113 }
114 
115