1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 28 мая 2019 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/plugins/room_builder_ui.h>
23 #include <plugins/room_builder.h>
24 #include <metadata/plugins.h>
25 #include <metadata/ports.h>
26 
27 namespace lsp
28 {
CtlFloatPort(room_builder_ui * ui,const char * pattern,const port_t * meta)29     room_builder_ui::CtlFloatPort::CtlFloatPort(room_builder_ui *ui, const char *pattern, const port_t *meta):
30         CtlPort(meta)
31     {
32         pUI         = ui;
33         sPattern    = pattern;
34 
35         char name[0x100];
36         ::sprintf(name, "/scene/object/*/%s", sPattern);
37         osc::pattern_create(&sOscPattern, name);
38         fValue      = get_default_value();
39     }
40 
~CtlFloatPort()41     room_builder_ui::CtlFloatPort::~CtlFloatPort()
42     {
43         pUI         = NULL;
44         sPattern    = NULL;
45         osc::pattern_destroy(&sOscPattern);
46     }
47 
name()48     const char *room_builder_ui::CtlFloatPort::name()
49     {
50         const char *format = NULL;
51         return (osc::pattern_get_format(&sOscPattern, &format) == STATUS_OK) ? format : NULL;
52     }
53 
get_value()54     float room_builder_ui::CtlFloatPort::get_value()
55     {
56         // Prepare the value
57         char name[0x100];
58         float value = 0.0f;
59         ::sprintf(name, "/scene/object/%d/%s", int(pUI->nSelected), sPattern);
60 
61         // Fetch value
62         KVTStorage *kvt = pUI->kvt_lock();
63         status_t res = STATUS_NOT_FOUND;
64         if (kvt != NULL)
65         {
66             res = kvt->get(name, &value);
67             pUI->kvt_release();
68         }
69 
70         // Return the limited value
71         return fValue = (res == STATUS_OK) ?
72                 limit_value(pMetadata, value) :
73                 get_default_value();
74     }
75 
set_value(float value)76     void room_builder_ui::CtlFloatPort::set_value(float value)
77     {
78         // Do not update value
79         if (fValue == value)
80             return;
81 
82         // Prepare the value
83         char name[0x100];
84         sprintf(name, "/scene/object/%d/%s", int(pUI->nSelected), sPattern);
85         value       = limit_value(pMetadata, value);
86 
87         // Obtain KVT storage
88         KVTStorage *kvt = pUI->kvt_lock();
89         if (kvt != NULL)
90         {
91             kvt_param_t param;
92             param.type  = KVT_FLOAT32;
93             param.f32   = value;
94 
95             // Write in silent mode
96             if (kvt->put(name, &param, KVT_TO_DSP) == STATUS_OK)
97             {
98                 fValue = value;
99                 pUI->kvt_write(kvt, name, &param);
100             }
101             pUI->kvt_release();
102         }
103     }
104 
match(const char * id)105     bool room_builder_ui::CtlFloatPort::match(const char *id)
106     {
107         return (osc::pattern_match(&sOscPattern, id) == STATUS_OK);
108     }
109 
changed(KVTStorage * storage,const char * id,const kvt_param_t * value)110     bool room_builder_ui::CtlFloatPort::changed(KVTStorage *storage, const char *id, const kvt_param_t *value)
111     {
112         char name[0x100];
113         ::sprintf(name, "/scene/object/%d/%s", int(pUI->nSelected), sPattern);
114         if (::strcmp(name, id) != 0)
115             return false;
116 
117         notify_all();
118         return true;
119     }
120 
121     static const char *UNNAMED_STR = "<unnamed>";
122 
CtlListPort(room_builder_ui * ui,const port_t * meta)123     room_builder_ui::CtlListPort::CtlListPort(room_builder_ui *ui, const port_t *meta):
124         CtlPort(&sMetadata)
125     {
126         pUI         = ui;
127         sMetadata   = *meta;
128         nItems      = 0;
129         nCapacity   = 0;
130         pItems      = NULL;
131         nSelectedReq= -1;
132 
133         osc::pattern_create(&sOscPattern, "/scene/object/*/name");
134     }
135 
~CtlListPort()136     room_builder_ui::CtlListPort::~CtlListPort()
137     {
138         vKvtPorts.flush();
139 
140         if (pItems != NULL)
141         {
142             for (size_t i=0; i<nCapacity; ++i)
143             {
144                 char *s = const_cast<char *>(pItems[i].text);
145                 if ((s != NULL) && (s != UNNAMED_STR))
146                     ::free(s);
147                 pItems[i].text = NULL;
148             }
149 
150             ::free(pItems);
151             pItems      = NULL;
152         }
153 
154         osc::pattern_destroy(&sOscPattern);
155     }
156 
name()157     const char *room_builder_ui::CtlListPort::name()
158     {
159         return "/scene/objects";
160     }
161 
get_value()162     float room_builder_ui::CtlListPort::get_value()
163     {
164         ssize_t index = pUI->nSelected;
165         if (nItems > 0)
166         {
167             if (index >= ssize_t(nItems))
168                 index   = nItems-1;
169             else if (index < 0)
170                 index   = 0;
171         }
172         else
173             index = -1;
174 
175         return index;
176     }
177 
set_value(float value)178     void room_builder_ui::CtlListPort::set_value(float value)
179     {
180         ssize_t index   = value;
181         if (index == pUI->nSelected)
182             return;
183 
184         pUI->nSelected  = index;
185 
186         // Deploy new value to KVT
187         KVTStorage *kvt = pUI->kvt_lock();
188         if (kvt != NULL)
189         {
190             kvt_param_t p;
191             p.type      = KVT_FLOAT32;
192             p.f32       = index;
193             kvt->put("/scene/selected", &p, KVT_RX);
194             pUI->kvt_write(kvt, "/scene/selected", &p);
195             pUI->kvt_release();
196         }
197 
198         // Notify all KVT ports
199         for (size_t i=0, n=vKvtPorts.size(); i<n; ++i)
200         {
201             CtlPort *p = vKvtPorts.get(i);
202             if (p != NULL)
203                 p->notify_all();
204         }
205     }
206 
add_port(CtlPort * port)207     void room_builder_ui::CtlListPort::add_port(CtlPort *port)
208     {
209         vKvtPorts.add(port);
210     }
211 
set_list_item(size_t id,const char * value)212     void room_builder_ui::CtlListPort::set_list_item(size_t id, const char *value)
213     {
214         if (pItems == NULL)
215             return;
216         char **v = const_cast<char **>(&pItems[id].text);
217 
218         // Free previous value holder
219         if ((*v != NULL) && (*v != UNNAMED_STR))
220             ::free(*v);
221 
222         // Try to copy name of parameter
223         if (value != NULL)
224             *v = ::strdup(value);
225         else if (::asprintf(v, "<unnamed #%d>", int(id)) < 0)
226             *v  = NULL;
227 
228         // If all is bad, do this
229         if (*v == NULL)
230             *v  = const_cast<char *>(UNNAMED_STR);
231     }
232 
match(const char * id)233     bool room_builder_ui::CtlListPort::match(const char *id)
234     {
235         if (!strcmp(id, "/scene/objects"))
236             return true;
237         if (!strcmp(id, "/scene/selected"))
238             return true;
239         return osc::pattern_match(&sOscPattern, id);
240     }
241 
changed(KVTStorage * storage,const char * id,const kvt_param_t * value)242     bool room_builder_ui::CtlListPort::changed(KVTStorage *storage, const char *id, const kvt_param_t *value)
243     {
244         if ((value->type == KVT_INT32) && (!strcmp(id, "/scene/objects")))
245         {
246             // Ensure that we have enough place to store object names
247             size_t size     = (value->i32 < 0) ? 0 : value->i32;
248             if (nItems == size)
249                 return false;
250 
251             // Compute the capacity and adjust array size
252             size_t capacity = ((size + 0x10) / 0x10) * 0x10;
253             if (capacity > nCapacity)
254             {
255                 port_item_t *list = reinterpret_cast<port_item_t *>(::realloc(pItems, capacity * sizeof(port_item_t)));
256                 if (list == NULL)
257                     return false;
258                 for (size_t i=nCapacity; i<capacity; ++i)
259                 {
260                     list[i].text    = NULL;
261                     list[i].lc_key  = NULL;
262                 }
263 
264                 pItems          = list;
265                 nCapacity       = capacity;
266                 sMetadata.items = list;
267             }
268 
269             // Allocate non-allocated strings
270             char pname[0x100]; // Should be enough
271             for (size_t i=nItems; i < size; ++i)
272             {
273                 ::snprintf(pname, sizeof(pname), "/scene/object/%d/name", int(i));
274                 const char *pval = NULL;
275                 status_t res = storage->get(pname, &pval);
276                 set_list_item(i, (res == STATUS_OK) ? pval : NULL);
277             }
278             nItems  = size; // Update size
279 
280             // Set the end of string list
281             char **s = const_cast<char **>(&pItems[nItems].text);
282             if ((*s != NULL) && (*s != UNNAMED_STR))
283                 ::free(*s);
284             *s = NULL;
285 
286             // Cleanup storage
287             room_builder_base::kvt_cleanup_objects(storage, nItems);
288 
289             // Change selected value
290             ssize_t index = pUI->nSelected;
291             if (storage->get(id, &value) == STATUS_OK)
292             {
293                 if (value->type == KVT_FLOAT32)
294                     index   = value->f32;
295             }
296 
297             if (index < 0)
298                 index = 0;
299             else if (index >= ssize_t(nItems))
300                 index = nItems-1;
301             set_value(index);           // Update the current selected value
302 
303             sync_metadata();            // Call for metadata update
304             notify_all();               // Notify all bound listeners
305             return true;
306         }
307         else if ((value->type == KVT_FLOAT32) && (!strcmp(id, "/scene/selected")))
308         {
309             set_value(value->f32);
310         }
311         else if ((value->type == KVT_STRING) && (::strstr(id, "/scene/object/") == id))
312         {
313             id += ::strlen("/scene/object/");
314 
315             char *endptr = NULL;
316             errno = 0;
317             long index = ::strtol(id, &endptr, 10);
318 
319             // Valid object number?
320             if ((errno == 0) && (!::strcmp(endptr, "/name")) &&
321                 (index >= 0) && (index < ssize_t(nItems)))
322             {
323                 set_list_item(index, value->str);   // Update list element
324                 sync_metadata();                    // Synchronize metadata
325                 return true;
326             }
327         }
328 
329         return false;
330     }
331 
CtlMaterialPreset(room_builder_ui * ui)332     room_builder_ui::CtlMaterialPreset::CtlMaterialPreset(room_builder_ui *ui)
333     {
334         pUI         = ui;
335         pCBox       = NULL;
336         hHandler    = -1;
337         pSpeed      = NULL;
338         pAbsorption = NULL;
339         pSelected   = NULL;
340     }
341 
~CtlMaterialPreset()342     room_builder_ui::CtlMaterialPreset::~CtlMaterialPreset()
343     {
344         pSpeed      = NULL;
345         pAbsorption = NULL;
346         pSelected   = NULL;
347     }
348 
init(const char * preset,const char * selected,const char * speed,const char * absorption)349     void room_builder_ui::CtlMaterialPreset::init(const char *preset, const char *selected, const char *speed, const char *absorption)
350     {
351         // Just bind ports
352         pSpeed      = pUI->port(speed);
353         pAbsorption = pUI->port(absorption);
354         pSelected   = pUI->port(selected);
355 
356         // Fetch widget
357         pCBox = widget_cast<LSPComboBox>(pUI->resolve("mpreset"));
358 
359         // Initialize list of presets
360         LSPItem li;
361         LSPString lc;
362         if (pCBox != NULL)
363         {
364             // Initialize box
365             li.text()->set("lists.room_bld.select_mat");
366             li.set_value(-1.0f);
367             pCBox->items()->add(&li);
368             size_t i=0;
369             for (const room_material_t *m = room_builder_base_metadata::materials; m->name != NULL; ++m)
370             {
371                 if (m->lc_key != NULL)
372                 {
373                     lc.set_ascii("lists.");
374                     lc.append_ascii(m->lc_key);
375                     li.text()->set(&lc);
376                 }
377                 else
378                     li.text()->set_raw(m->name);
379                 li.set_value(i++);
380                 pCBox->items()->add(&li);
381             }
382             pCBox->set_selected(0);
383 
384             // Bind listener
385             hHandler    = pCBox->slots()->bind(LSPSLOT_CHANGE, slot_change, this);
386         }
387 
388         // Bind handlers and notify changes
389         if (pSpeed != NULL)
390         {
391             pSpeed->bind(this);
392             pSpeed->notify_all();
393         }
394         if (pAbsorption != NULL)
395         {
396             pAbsorption->bind(this);
397             pAbsorption->notify_all();
398         }
399         if (pSelected != NULL)
400         {
401             pSelected->bind(this);
402             pSelected->notify_all();
403         }
404     }
405 
slot_change(LSPWidget * sender,void * ptr,void * data)406     status_t room_builder_ui::CtlMaterialPreset::slot_change(LSPWidget *sender, void *ptr, void *data)
407     {
408         CtlMaterialPreset *_this = reinterpret_cast<CtlMaterialPreset *>(ptr);
409         if (ptr == NULL)
410             return STATUS_BAD_STATE;
411 
412         ssize_t sel = _this->pSelected->get_value();
413         if (sel < 0)
414             return STATUS_OK;
415 
416         ssize_t idx = (_this->pCBox != NULL) ? _this->pCBox->selected() - 1 : -1;
417         if (idx < 0)
418             return STATUS_OK;
419 
420         const room_material_t *m = &room_builder_base_metadata::materials[idx];
421 
422         if (_this->pAbsorption->get_value() != m->absorption)
423         {
424             _this->pAbsorption->set_value(m->absorption);
425             _this->pAbsorption->notify_all();
426         }
427 
428         if (_this->pSpeed->get_value() != m->speed)
429         {
430             _this->pSpeed->set_value(m->speed);
431             _this->pSpeed->notify_all();
432         }
433 
434         return STATUS_OK;
435     }
436 
notify(CtlPort * port)437     void room_builder_ui::CtlMaterialPreset::notify(CtlPort *port)
438     {
439         if (pCBox == NULL)
440             return;
441 
442         float fAbsorption   = pAbsorption->get_value();
443         float fSpeed        = pSpeed->get_value();
444 
445         // Find best match
446         ssize_t idx = 0, i = 1;
447         for (const room_material_t *m = room_builder_base_metadata::materials; m->name != NULL; ++m, ++i)
448         {
449             if ((m->speed == fSpeed) && (m->absorption == fAbsorption))
450             {
451                 idx = i;
452                 break;
453             }
454         }
455 
456         // Set-up selected index in non-notify mode
457         if (pCBox->selected() != idx)
458         {
459             pCBox->slots()->disable(LSPSLOT_CHANGE, hHandler);
460             pCBox->set_selected(idx);
461             pCBox->slots()->enable(LSPSLOT_CHANGE, hHandler);
462         }
463     }
464 
CtlKnobBinding(room_builder_ui * ui,bool reverse)465     room_builder_ui::CtlKnobBinding::CtlKnobBinding(room_builder_ui *ui, bool reverse)
466     {
467         pUI         = ui;
468         pOuter      = NULL;
469         pInner      = NULL;
470         pLink       = NULL;
471         bReverse    = reverse;
472     }
473 
~CtlKnobBinding()474     room_builder_ui::CtlKnobBinding::~CtlKnobBinding()
475     {
476         pUI         = NULL;
477         pOuter      = NULL;
478         pInner      = NULL;
479         pLink       = NULL;
480         bReverse    = false;
481     }
482 
init(const char * outer,const char * inner,const char * link)483     void room_builder_ui::CtlKnobBinding::init(const char *outer, const char *inner, const char *link)
484     {
485         // Just bind ports
486         pOuter      = pUI->port(outer);
487         pInner      = pUI->port(inner);
488         pLink       = pUI->port(link);
489 
490         // Bind handlers and notify changes
491         if (pLink != NULL)
492         {
493             pLink->bind(this);
494             pLink->notify_all();
495         }
496         if (pInner != NULL)
497         {
498             pInner->bind(this);
499             pInner->notify_all();
500         }
501         if (pOuter != NULL)
502         {
503             pOuter->bind(this);
504             pOuter->notify_all();
505         }
506     }
507 
notify(CtlPort * port)508     void room_builder_ui::CtlKnobBinding::notify(CtlPort *port)
509     {
510         if (port == NULL)
511             return;
512 
513         bool link = (pLink != NULL) ? pLink->get_value() >= 0.5f : false;
514         if (!link)
515             return;
516 
517         if (port == pLink)
518             port = pOuter;
519 
520         if ((port == pInner) && (pInner != NULL))
521         {
522             const port_t *meta = pInner->metadata();
523             float v = pInner->get_value();
524             if (bReverse)
525                 v = meta->max - v;
526 
527             if (pOuter->get_value() != v)
528             {
529                 pOuter->set_value(v);
530                 pOuter->notify_all();
531             }
532         }
533         else if ((port == pOuter) && (pOuter != NULL))
534         {
535             const port_t *meta = pOuter->metadata();
536             float v = pOuter->get_value();
537             if (bReverse)
538                 v = meta->max - v;
539 
540             if (pInner->get_value() != v)
541             {
542                 pInner->set_value(v);
543                 pInner->notify_all();
544             }
545         }
546     }
547 
548     //-------------------------------------------------------------------------
549     // Main class methods
550 
room_builder_ui(const plugin_metadata_t * mdata,void * root_widget)551     room_builder_ui::room_builder_ui(const plugin_metadata_t *mdata, void *root_widget):
552         plugin_ui(mdata, root_widget),
553         sPresets(this),
554         sAbsorption(this, false),
555         sTransparency(this, true),
556         sDispersion(this, false),
557         sDiffuse(this, false)
558     {
559         nSelected       = -1;
560     }
561 
~room_builder_ui()562     room_builder_ui::~room_builder_ui()
563     {
564     }
565 
init(IUIWrapper * wrapper,int argc,const char ** argv)566     status_t room_builder_ui::init(IUIWrapper *wrapper, int argc, const char **argv)
567     {
568         status_t res = plugin_ui::init(wrapper, argc, argv);
569         if (res != STATUS_OK)
570             return res;
571 
572         const port_t *meta = room_builder_base_metadata::kvt_ports;
573 
574         // Create object identifier port
575         CtlListPort *kvt_list = new CtlListPort(this, meta++);
576         if (kvt_list == NULL)
577             return STATUS_NO_MEM;
578         add_custom_port(kvt_list);
579         add_kvt_listener(kvt_list);
580 
581         CtlFloatPort *p;
582 
583 #define BIND_KVT_PORT(pattern, field)    \
584         p = new CtlFloatPort(this, pattern, meta++); \
585         if (p == NULL) \
586             return STATUS_NO_MEM; \
587         kvt_list->add_port(p); \
588         add_custom_port(p); \
589         add_kvt_listener(p);
590 
591         BIND_KVT_PORT("enabled", fEnabled);
592         BIND_KVT_PORT("position/x", sPos.x);
593         BIND_KVT_PORT("position/y", sPos.y);
594         BIND_KVT_PORT("position/z", sPos.z);
595         BIND_KVT_PORT("rotation/yaw", fYaw);
596         BIND_KVT_PORT("rotation/pitch", fPitch);
597         BIND_KVT_PORT("rotation/roll", fRoll);
598         BIND_KVT_PORT("scale/x", fSizeX);
599         BIND_KVT_PORT("scale/y", fSizeY);
600         BIND_KVT_PORT("scale/z", fSizeZ);
601         BIND_KVT_PORT("color/hue", fHue);
602         BIND_KVT_PORT("material/absorption/outer", fAbsorption[0]);
603         BIND_KVT_PORT("material/absorption/inner", fAbsorption[1]);
604         BIND_KVT_PORT("material/absorption/link", lnkAbsorption);
605         BIND_KVT_PORT("material/dispersion/outer", fDispersion[0]);
606         BIND_KVT_PORT("material/dispersion/inner", fDispersion[1]);
607         BIND_KVT_PORT("material/dispersion/link", lnkDispersion);
608         BIND_KVT_PORT("material/diffusion/outer", fDiffusion[0]);
609         BIND_KVT_PORT("material/diffusion/inner", fDiffusion[1]);
610         BIND_KVT_PORT("material/diffusion/link", lnkDiffusion);
611         BIND_KVT_PORT("material/transparency/outer", fTransparency[1]);
612         BIND_KVT_PORT("material/transparency/inner", fTransparency[1]);
613         BIND_KVT_PORT("material/transparency/link", lnkTransparency);
614         BIND_KVT_PORT("material/sound_speed", fSndSpeed);
615 
616         sAbsorption.init("kvt:oabs", "kvt:iabs", "kvt:labs");
617         sTransparency.init("kvt:otransp", "kvt:itransp", "kvt:ltransp");
618         sDispersion.init("kvt:odisp", "kvt:idisp", "kvt:ldisp");
619         sDiffuse.init("kvt:odiff", "kvt:idiff", "kvt:ldiff");
620 
621         return STATUS_OK;
622     }
623 
build()624     status_t room_builder_ui::build()
625     {
626         status_t res = plugin_ui::build();
627         if (res == STATUS_OK)
628             sPresets.init("mpreset", "kvt:oid", "kvt:speed", "kvt:oabs");
629         return res;
630     }
631 
632 } /* namespace lsp */
633