1 /*
2 * Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #include <time.h>
19 #include <math.h>
20 #include <inttypes.h>
21
22 #include <sherlock.h>
23 #include <sherlock_nk.h>
24 #include <encoder.h>
25
26 #include <osc.lv2/util.h>
27
28 typedef struct _mem_t mem_t;
29
30 struct _mem_t {
31 size_t size;
32 char *buf;
33 };
34
35 static inline void
_shadow(struct nk_context * ctx,bool * shadow)36 _shadow(struct nk_context *ctx, bool *shadow)
37 {
38 if(*shadow)
39 {
40 struct nk_style *style = &ctx->style;
41 const struct nk_vec2 group_padding = style->window.group_padding;
42 struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
43
44 struct nk_rect b = nk_widget_bounds(ctx);
45 b.x -= group_padding.x;
46 b.w *= 10;
47 b.w += 5*group_padding.x;
48 nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
49 }
50
51 *shadow = !*shadow;
52 }
53
54 static inline void
_mem_printf(mem_t * mem,const char * fmt,...)55 _mem_printf(mem_t *mem, const char *fmt, ...)
56 {
57 va_list args;
58 va_start(args, fmt);
59
60 char *src;
61 if(vasprintf(&src, fmt, args) != -1)
62 {
63 const size_t sz = strlen(src);
64
65 mem->buf = realloc(mem->buf, mem->size + sz);
66 if(mem->buf)
67 {
68 char *dst = mem->buf + mem->size - 1;
69
70 strncpy(dst, src, sz + 1);
71 mem->size += sz;
72 }
73 else
74 mem->size = 0;
75
76 free(src);
77 }
78
79 va_end(args);
80 }
81
82 static void
_osc_timetag(mem_t * mem,LV2_OSC_Timetag * tt)83 _osc_timetag(mem_t *mem, LV2_OSC_Timetag *tt)
84 {
85 if(tt->integral <= 1UL)
86 {
87 _mem_printf(mem, "t : %s", "immediate");
88 }
89 else
90 {
91 const uint32_t us = floor(tt->fraction * 0x1p-32 * 1e6);
92 const time_t ttime = tt->integral - 0x83aa7e80;
93 const struct tm *ltime = localtime(&ttime);
94
95 char tmp [32];
96 if(strftime(tmp, 32, "%d-%b-%Y %T", ltime))
97 _mem_printf(mem, "t : %s.%06"PRIu32, tmp, us);
98 }
99 }
100
101 static void
_osc_argument(plughandle_t * handle,struct nk_context * ctx,const LV2_Atom * arg,const char * path,bool first)102 _osc_argument(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom *arg,
103 const char *path, bool first)
104 {
105 const LV2_OSC_Type type = lv2_osc_argument_type(&handle->osc_urid, arg);
106
107 mem_t mem = {
108 .size = 1,
109 .buf = calloc(1, sizeof(char))
110 };
111
112 switch(type)
113 {
114 case LV2_OSC_INT32:
115 {
116 int32_t i;
117 lv2_osc_int32_get(&handle->osc_urid, arg, &i);
118 _mem_printf(&mem, "i : %"PRIi32, i);
119 } break;
120 case LV2_OSC_FLOAT:
121 {
122 float f;
123 lv2_osc_float_get(&handle->osc_urid, arg, &f);
124 _mem_printf(&mem, "f : %f", f);
125 } break;
126 case LV2_OSC_STRING:
127 {
128 const char *s;
129 lv2_osc_string_get(&handle->osc_urid, arg, &s);
130 _mem_printf(&mem, "s : %s", s);
131 } break;
132 case LV2_OSC_BLOB:
133 {
134 uint32_t sz;
135 const uint8_t *b;
136 lv2_osc_blob_get(&handle->osc_urid, arg, &sz, &b);
137 _mem_printf(&mem, "b : (%"PRIu32") ", sz);
138 for(unsigned i=0; i<sz; i++)
139 _mem_printf(&mem, "%02"PRIx8, b[i]);
140 } break;
141
142 case LV2_OSC_TRUE:
143 {
144 _mem_printf(&mem, "T : true");
145 } break;
146 case LV2_OSC_FALSE:
147 {
148 _mem_printf(&mem, "F : false");
149 } break;
150 case LV2_OSC_NIL:
151 {
152 _mem_printf(&mem, "N : nil");
153 } break;
154 case LV2_OSC_IMPULSE:
155 {
156 _mem_printf(&mem, "I : impulse");
157 } break;
158
159 case LV2_OSC_INT64:
160 {
161 int64_t h;
162 lv2_osc_int64_get(&handle->osc_urid, arg, &h);
163 _mem_printf(&mem, "h : %"PRIi64, h);
164 } break;
165 case LV2_OSC_DOUBLE:
166 {
167 double d;
168 lv2_osc_double_get(&handle->osc_urid, arg, &d);
169 _mem_printf(&mem, "d : %lf", d);
170 } break;
171 case LV2_OSC_TIMETAG:
172 {
173 LV2_OSC_Timetag tt;
174 lv2_osc_timetag_get(&handle->osc_urid, arg, &tt);
175 _osc_timetag(&mem, &tt);
176 } break;
177
178 case LV2_OSC_SYMBOL:
179 {
180 LV2_URID S;
181 lv2_osc_symbol_get(&handle->osc_urid, arg, &S);
182 _mem_printf(&mem, "S : %s", handle->unmap->unmap(handle->unmap->handle, S));
183 } break;
184 case LV2_OSC_CHAR:
185 {
186 char c;
187 lv2_osc_char_get(&handle->osc_urid, arg, &c);
188 _mem_printf(&mem, "c : %c", c);
189 } break;
190 case LV2_OSC_RGBA:
191 {
192 uint8_t r [4];
193 lv2_osc_rgba_get(&handle->osc_urid, arg, r+0, r+1, r+2, r+3);
194 _mem_printf(&mem, "r : ");
195 for(unsigned i=0; i<4; i++)
196 _mem_printf(&mem, "%02"PRIx8, r[i]);
197 } break;
198 case LV2_OSC_MIDI:
199 {
200 uint32_t sz;
201 const uint8_t *m;
202 lv2_osc_midi_get(&handle->osc_urid, arg, &sz, &m);
203 _mem_printf(&mem, "m : (%"PRIu32") ", sz);
204 for(unsigned i=0; i<sz; i++)
205 _mem_printf(&mem, "%02"PRIx8, m[i]);
206 } break;
207 }
208
209 if(!first)
210 {
211 _shadow(ctx, &handle->shadow);
212 _empty(ctx);
213 }
214
215 if(first)
216 {
217 nk_label_colored(ctx, path, NK_TEXT_LEFT, magenta);
218 }
219 else
220 {
221 _empty(ctx);
222 }
223
224 if(mem.buf)
225 {
226 nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, cwhite);
227 free(mem.buf);
228 }
229 else
230 {
231 _empty(ctx);
232 }
233
234 nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, arg->size);
235 }
236
237 static void
_osc_message(plughandle_t * handle,struct nk_context * ctx,const LV2_Atom_Object * obj)238 _osc_message(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj)
239 {
240 const LV2_Atom_String *path = NULL;
241 const LV2_Atom_Tuple *args = NULL;
242 lv2_osc_message_get(&handle->osc_urid, obj, &path, &args);
243
244 bool first = true;
245 LV2_ATOM_TUPLE_FOREACH(args, arg)
246 {
247 _osc_argument(handle, ctx, arg, LV2_ATOM_BODY_CONST(path), first);
248
249 if(first)
250 first = false;
251 }
252 }
253
254 static void
255 _osc_packet(plughandle_t *handle, struct nk_context *ctx, int64_t frames,
256 const LV2_Atom_Object *obj, float offset);
257
258 static void
_osc_bundle(plughandle_t * handle,struct nk_context * ctx,const LV2_Atom_Object * obj,float offset)259 _osc_bundle(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset)
260 {
261 const LV2_Atom_Object *timetag = NULL;
262 const LV2_Atom_Tuple *items = NULL;
263 lv2_osc_bundle_get(&handle->osc_urid, obj, &timetag, &items);
264
265 // format bundle timestamp
266 LV2_OSC_Timetag tt;
267 lv2_osc_timetag_get(&handle->osc_urid, &timetag->atom, &tt);
268
269 mem_t mem = {
270 .size = 1,
271 .buf = calloc(1, sizeof(char))
272 };
273
274 nk_label_colored(ctx, "#bundle", NK_TEXT_LEFT, red);
275
276 _osc_timetag(&mem, &tt);
277 if(mem.buf)
278 {
279 nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, cwhite);
280 free(mem.buf);
281 }
282 else
283 {
284 _empty(ctx);
285 }
286
287 nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, obj->atom.size);
288
289 LV2_ATOM_TUPLE_FOREACH(items, item)
290 {
291 _osc_packet(handle, ctx, -1, (const LV2_Atom_Object *)item, offset + 0.025);
292 }
293 }
294
295 static void
_osc_packet(plughandle_t * handle,struct nk_context * ctx,int64_t frames,const LV2_Atom_Object * obj,float offset)296 _osc_packet(plughandle_t *handle, struct nk_context *ctx, int64_t frames,
297 const LV2_Atom_Object *obj, float offset)
298 {
299 const float widget_h = handle->dy;
300
301 const float ratios [4] = {offset, 0.3f - offset, 0.6f, 0.1f};
302 nk_layout_row(ctx, NK_DYNAMIC, widget_h, 4, ratios);
303
304 _shadow(ctx, &handle->shadow);
305 if(frames >= 0)
306 {
307 nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
308 }
309 else
310 {
311 _empty(ctx);
312 }
313
314 if(lv2_osc_is_message_type(&handle->osc_urid, obj->body.otype))
315 {
316 _osc_message(handle, ctx, obj);
317 }
318 else if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
319 {
320 _osc_bundle(handle, ctx, obj, offset);
321 }
322 }
323
324 void
_osc_inspector_expose(struct nk_context * ctx,struct nk_rect wbounds,void * data)325 _osc_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
326 {
327 plughandle_t *handle = data;
328
329 handle->dy = 20.f * _get_scale(handle);
330 const float widget_h = handle->dy;
331 struct nk_style *style = &ctx->style;
332 const struct nk_vec2 window_padding = style->window.padding;
333 const struct nk_vec2 group_padding = style->window.group_padding;
334
335 const char *window_name = "Sherlock";
336 if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
337 {
338 struct nk_panel *panel = nk_window_get_panel(ctx);
339 struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
340
341 const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
342 nk_layout_row_dynamic(ctx, body_h, 1);
343 nk_flags flags = NK_WINDOW_BORDER;
344 if(handle->state.follow)
345 {
346 flags |= NK_WINDOW_NO_SCROLLBAR;
347 }
348 else
349 {
350 handle->shadow = false;
351 }
352 struct nk_list_view lview;
353 if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
354 {
355 if(handle->state.follow)
356 {
357 lview.end = NK_MAX(handle->n_item, 0);
358 lview.begin = NK_MAX(lview.end - lview.count, 0);
359 }
360 for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
361 {
362 item_t *itm = handle->items[l];
363
364 switch(itm->type)
365 {
366 case ITEM_TYPE_NONE:
367 {
368 // skip, was bundle payload
369 } break;
370 case ITEM_TYPE_FRAME:
371 {
372 nk_layout_row_dynamic(ctx, widget_h, 3);
373 {
374 struct nk_rect b = nk_widget_bounds(ctx);
375 b.x -= group_padding.x;
376 b.w *= 3;
377 b.w += 4*group_padding.x;
378 nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
379 }
380
381 nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
382 nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
383 nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
384
385 handle->shadow = false;
386 } break;
387
388 case ITEM_TYPE_EVENT:
389 {
390 LV2_Atom_Event *ev = &itm->event.ev;
391 const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
392 const int64_t frames = ev->time.frames;
393 const float offset = 0.1;
394
395 _osc_packet(handle, ctx, frames, obj, offset);
396 } break;
397 }
398 }
399
400 nk_list_view_end(&lview);
401 }
402
403 const float n = 3;
404 const float r0 = 1.f / n;
405 const float r1 = 0.1f / 3;
406 const float r2 = r0 - r1;
407 const float footer [6] = {r1, r2, r1, r2, r1, r2};
408 nk_layout_row(ctx, NK_DYNAMIC, widget_h, 6, footer);
409 {
410 const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
411 if(state_overwrite != handle->state.overwrite)
412 {
413 handle->state.overwrite = state_overwrite;
414 _set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
415 }
416 nk_label(ctx, "overwrite", NK_TEXT_LEFT);
417
418 const int32_t state_block = _check(ctx, handle->state.block);
419 if(state_block != handle->state.block)
420 {
421 handle->state.block = state_block;
422 _set_bool(handle, handle->urid.block, handle->state.block);
423 }
424 nk_label(ctx, "block", NK_TEXT_LEFT);
425
426 const int32_t state_follow = _check(ctx, handle->state.follow);
427 if(state_follow != handle->state.follow)
428 {
429 handle->state.follow = state_follow;
430 _set_bool(handle, handle->urid.follow, handle->state.follow);
431 }
432 nk_label(ctx, "follow", NK_TEXT_LEFT);
433 }
434
435 const bool max_reached = handle->n_item >= MAX_LINES;
436 nk_layout_row_dynamic(ctx, widget_h, 2);
437 if(nk_button_symbol_label(ctx,
438 max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
439 "clear", NK_TEXT_LEFT))
440 {
441 _clear(handle);
442 }
443 nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
444 }
445 nk_end(ctx);
446 }
447