api_osc.c 7.51 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the Artistic License 2.0 as published by
 * The Perl Foundation.
 *
 * This source 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
 * Artistic License 2.0 for more details.
 *
 * You should have received a copy of the Artistic License 2.0
 * along the source as a COPYING file. If not, obtain it from
 * http://www.perlfoundation.org/artistic_license_2_0.
 */

18 19
#include <math.h>

20 21
#include <api_osc.h>
#include <api_atom.h>
22
#include <api_forge.h>
23

24 25
#include <osc.lv2/util.h>

26 27 28 29
typedef struct _osc_responder_data_t osc_responder_data_t;

struct _osc_responder_data_t {
	moony_t *moony;
Hanspeter Portner's avatar
Hanspeter Portner committed
30
	bool matched;
31 32
};

33
__realtime static inline bool
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
_osc_path_has_wildcards(const char *path)
{
	for(const char *ptr=path+1; *ptr; ptr++)
		if(strchr("?*[{", *ptr))
			return true;
	return false;
}

//TODO can we rewrite this directly in C?
const char *loscresponder_match =
	"function __expand(v)\n"
	"	return coroutine.wrap(function()\n"
	"		local item = string.match(v, '%b{}')\n"
	"		if item then\n"
	"			for key in string.gmatch(item, '{?([^,}]*)') do\n"
	"				local vv = string.gsub(v, item, key)\n"
	"				for w in __expand(vv) do\n"
	"					coroutine.yield(w)\n"
	"				end\n"
	"			end\n"
	"		else\n"
	"			coroutine.yield(v)\n"
	"		end\n"
	"	end)\n"
	"end\n"
	"\n"
	"function __match(v, o, ...)\n"
Hanspeter Portner's avatar
Hanspeter Portner committed
61
	"	local matched = false\n"
62 63 64 65 66 67 68
	"	v = string.gsub(v, '%?', '.')\n"
	"	v = string.gsub(v, '%*', '.*')\n"
	"	v = string.gsub(v, '%[%!', '[^')\n"
	"	v = '^' .. v .. '$'\n"
	"	for w in __expand(v) do\n"
	"		for k, x in pairs(o) do\n"
	"			if string.match(k, w) then\n"
69
	"				x(o, ...)\n"
Hanspeter Portner's avatar
Hanspeter Portner committed
70
	"				matched = matched or true\n"
71 72 73
	"			end\n"
	"		end\n"
	"	end\n"
Hanspeter Portner's avatar
Hanspeter Portner committed
74
	"	return matched\n"
75 76
	"end";

77
__realtime static inline void
78
_loscresponder_method(const char *path, const LV2_Atom_Tuple *arguments, void *data)
79
{
80 81
	osc_responder_data_t *ord = data;
	moony_t *moony = ord->moony;
82
	lua_State *L = moony_current(moony);
Hanspeter Portner's avatar
Hanspeter Portner committed
83
	//LV2_Atom_Forge *forge = &moony->forge;
84
	LV2_OSC_URID *osc_urid = &moony->osc_urid;
Hanspeter Portner's avatar
Hanspeter Portner committed
85
	//LV2_URID_Unmap *unmap = moony->unmap;
86 87 88 89 90

	// 1: uservalue
	// 2: frames
	// 3: data

Hanspeter Portner's avatar
Hanspeter Portner committed
91
	int has_wildcard = 0;
92 93
	if(_osc_path_has_wildcards(path))
	{
Hanspeter Portner's avatar
Hanspeter Portner committed
94
		lua_getglobal(L, "__match"); // push pattern has_wildcard function
95
		lua_pushstring(L, path); // path
Hanspeter Portner's avatar
Hanspeter Portner committed
96
		has_wildcard = 1;
97 98 99 100
	}
	else if(lua_getfield(L, 1, path) == LUA_TNIL) // raw string match
	{
		lua_pop(L, 1); // nil
Hanspeter Portner's avatar
Hanspeter Portner committed
101
		ord->matched = ord->matched || false;
102
		return;
103 104 105 106 107 108
	}

	lua_pushvalue(L, 1); // self
	lua_pushvalue(L, 2); // frames
	lua_pushvalue(L, 3); // data

109 110 111 112 113 114 115 116 117 118 119
	// push format string
	luaL_Buffer B;
	luaL_buffinit(L, &B);
	LV2_ATOM_TUPLE_FOREACH(arguments, atom)
	{
		const LV2_OSC_Type type = lv2_osc_argument_type(osc_urid, atom);
		if(type)
			luaL_addchar(&B, type);
	}
	luaL_pushresult(&B);

120 121
	int oldtop = lua_gettop(L);

122
	LV2_ATOM_TUPLE_FOREACH(arguments, atom)
123
	{
Hanspeter Portner's avatar
Hanspeter Portner committed
124
		//const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
125 126

		switch(lv2_osc_argument_type(osc_urid, atom))
127
		{
128
			case LV2_OSC_INT32:
129
			{
130 131 132
				int32_t i;
				if(lv2_osc_int32_get(osc_urid, atom, &i))
					lua_pushinteger(L, i);
133 134
				break;
			}
135
			case LV2_OSC_FLOAT:
136
			{
137 138 139
				float f;
				if(lv2_osc_float_get(osc_urid, atom, &f))
					lua_pushnumber(L, f);
140 141
				break;
			}
142
			case LV2_OSC_STRING:
143
			{
144 145 146
				const char *s;
				if(lv2_osc_string_get(osc_urid, atom, &s))
					lua_pushstring(L, s);
147 148
				break;
			}
149
			case LV2_OSC_BLOB:
150
			{
151 152 153 154
				const uint8_t *b;
				uint32_t len;
				if(lv2_osc_blob_get(osc_urid, atom, &len, &b))
					lua_pushlstring(L, (const char *)b, len);
155 156
				break;
			}
157 158 159

			case LV2_OSC_INT64:
			{
160 161 162
				int64_t h;
				if(lv2_osc_int64_get(osc_urid, atom, &h))
					lua_pushinteger(L, h);
163 164 165
				break;
			}
			case LV2_OSC_DOUBLE:
166
			{
167 168 169
				double d;
				if(lv2_osc_double_get(osc_urid, atom, &d))
					lua_pushnumber(L, d);
170 171
				break;
			}
172 173 174
			case LV2_OSC_TIMETAG:
			{
				LV2_OSC_Timetag tt;
175 176
				if(lv2_osc_timetag_get(osc_urid, atom, &tt))
					lua_pushinteger(L, lv2_osc_timetag_parse(&tt));
177 178 179 180
				break;
			}

			case LV2_OSC_TRUE:
181
			{
182
				lua_pushboolean(L, 1);
183 184
				break;
			}
185
			case LV2_OSC_FALSE:
186
			{
187
				lua_pushboolean(L, 0);
188 189
				break;
			}
190
			case LV2_OSC_NIL:
191
			{
192
				lua_pushnil(L);
193 194
				break;
			}
195
			case LV2_OSC_IMPULSE:
196
			{
197 198 199 200 201 202
				lua_pushnumber(L, HUGE_VAL);
				break;
			}

			case LV2_OSC_SYMBOL:
			{
Hanspeter Portner's avatar
Hanspeter Portner committed
203
				LV2_URID S;
204
				if(lv2_osc_symbol_get(&moony->osc_urid, atom, &S))
Hanspeter Portner's avatar
Hanspeter Portner committed
205
					lua_pushinteger(L, S);
206 207 208 209
				break;
			}
			case LV2_OSC_MIDI:
			{
210 211 212 213
				const uint8_t *m;
				uint32_t len;
				if(lv2_osc_midi_get(&moony->osc_urid, atom, &len, &m))
					lua_pushlstring(L, (const char *)m, len);
214 215
				break;
			}
216
			case LV2_OSC_CHAR:
217
			{
218 219 220
				char c;
				if(lv2_osc_char_get(&moony->osc_urid, atom, &c))
					lua_pushinteger(L, c);
221 222
				break;
			}
223
			case LV2_OSC_RGBA:
224
			{
225 226
				uint8_t r, g, b, a;
				if(lv2_osc_rgba_get(&moony->osc_urid, atom, &r, &g, &b, &a))
Hanspeter Portner's avatar
Hanspeter Portner committed
227
					lua_pushinteger(L, ((int64_t)r << 24) | (g << 16) | (b << 8) | a);
228 229 230 231 232
				break;
			}
		}
	}

Hanspeter Portner's avatar
Hanspeter Portner committed
233
	lua_call(L, 4 + has_wildcard + lua_gettop(L) - oldtop, has_wildcard);
234

Hanspeter Portner's avatar
Hanspeter Portner committed
235
	if(has_wildcard)
236
	{
Hanspeter Portner's avatar
Hanspeter Portner committed
237
		ord->matched = ord->matched || lua_toboolean(L, -1);
238 239
		lua_pop(L, 1);
	}
Hanspeter Portner's avatar
Hanspeter Portner committed
240
	else // raw string
241
	{
Hanspeter Portner's avatar
Hanspeter Portner committed
242
		ord->matched = ord->matched || true;
243
	}
244 245
}

246
__realtime static int
247 248 249
_loscresponder__call(lua_State *L)
{
	moony_t *moony = lua_touserdata(L, lua_upvalueindex(1));
250
	const bool *through = lua_touserdata(L, 1);
251 252 253 254 255 256 257

	lua_settop(L, 4); // discard superfluous arguments
	// 1: self
	// 2: frames
	// 3: data
	// 4: atom
	
258
	latom_t *latom = NULL;
259
	if(luaL_testudata(L, 4, "latom"))
260
		latom = lua_touserdata(L, 4);
261 262 263
	lua_pop(L, 1); // atom

	// check for valid atom and event type
Hanspeter Portner's avatar
Hanspeter Portner committed
264
	const LV2_Atom_Object *obj = latom ? (const LV2_Atom_Object *)latom->atom : NULL;
265 266 267 268

	if(  !latom
		|| !lv2_atom_forge_is_object_type(&moony->forge, obj->atom.type)
		|| !(lv2_osc_is_message_or_bundle_type(&moony->osc_urid, obj->body.otype)))
269 270 271 272 273 274 275 276 277
	{
		lua_pushboolean(L, 0); // not handled
		return 1;
	}

	// replace self with its uservalue
	lua_getuservalue(L, 1);
	lua_replace(L, 1);

278 279
	osc_responder_data_t ord = {
		.moony = moony,
Hanspeter Portner's avatar
Hanspeter Portner committed
280
		.matched = false
281 282 283 284 285
	};

	lv2_osc_body_unroll(&moony->osc_urid, latom->atom->size, latom->body.obj,
		_loscresponder_method, &ord);

Hanspeter Portner's avatar
Hanspeter Portner committed
286
	if(!ord.matched && *through) // not handled and through mode
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
	{
		const int64_t frames = luaL_checkinteger(L, 2);
		lforge_t *lforge = luaL_checkudata(L, 3, "lforge");

		if(frames < lforge->last.frames)
			luaL_error(L, "invalid frame time, must not decrease");
		lforge->last.frames = frames;

		if(  !lv2_atom_forge_frame_time(lforge->forge, frames)
			|| !lv2_atom_forge_atom(lforge->forge, latom->atom->size, latom->atom->type)
			|| !lv2_atom_forge_write(lforge->forge, latom->body.raw, latom->atom->size) )
			luaL_error(L, forge_buffer_overflow);
	}

	lua_pushboolean(L, 1); // handled
Hanspeter Portner's avatar
Hanspeter Portner committed
302 303
	lua_pushboolean(L, ord.matched); // matched a registered path
	return 2;
304 305
}

306
__realtime int
307 308 309 310
_loscresponder(lua_State *L)
{
	//moony_t *moony = lua_touserdata(L, lua_upvalueindex(1));
	
311 312 313 314
	lua_settop(L, 2); // discard superfluous arguments

	const bool _through = lua_toboolean(L, 2);
	lua_pop(L, 1); // bool
315 316

	// o = new 
317 318
	bool *through = lua_newuserdata(L, sizeof(bool));
	*through= _through;
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335

	// o.uservalue = uservalue
	lua_insert(L, 1);
	lua_setuservalue(L, -2);

	// setmetatable(o, self)
	luaL_getmetatable(L, "loscresponder");
	lua_setmetatable(L, -2);

	// return o
	return 1;
}

const luaL_Reg loscresponder_mt [] = {
	{"__call", _loscresponder__call},
	{NULL, NULL}
};