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, ¶m, KVT_TO_DSP) == STATUS_OK) 97 { 98 fValue = value; 99 pUI->kvt_write(kvt, name, ¶m); 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