1 /* 2 MIT License 3 4 Copyright (c) 2021 Alexandre BIQUE 5 Copyright (c) 2024 Guillaume PIOLAT 6 7 Permission is hereby granted, free of charge, to any person obtaining 8 a copy of this software and associated documentation files (the 9 "Software"), to deal in the Software without restriction, including 10 without limitation the rights to use, copy, modify, merge, publish, 11 distribute, sublicense, and/or sell copies of the Software, and to 12 permit persons to whom the Software is furnished to do so, subject to 13 the following conditions: 14 15 The above copyright notice and this permission notice shall be 16 included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 module dplug.clap.client; 27 28 nothrow @nogc: 29 version(CLAP): 30 31 import core.stdc.string: strcmp, strlen, memcpy; 32 import core.stdc.stdio: sscanf, snprintf; 33 import core.stdc.stdlib: free; 34 import core.stdc.math: isnan, isinf, isfinite; 35 36 import dplug.core.nogc; 37 import dplug.core.runtime; 38 import dplug.core.vec; 39 import dplug.core.sync; 40 41 import dplug.client.client; 42 import dplug.client.params; 43 import dplug.client.graphics; 44 import dplug.client.daw; 45 import dplug.client.preset; 46 import dplug.client.midi; 47 48 import dplug.clap.types; 49 50 //debug = clap; 51 debug(clap) import core.stdc.stdio; 52 53 static streq(const(char)* a, string b) pure 54 { 55 return strcmp(a, b.ptr) == 0; 56 } 57 58 class CLAPClient 59 { 60 public: 61 nothrow: 62 @nogc: 63 64 this(Client client, const(clap_host_t)* host) 65 { 66 _client = client; 67 _host = mallocNew!CLAPHost(this, host); 68 69 // fill _plugin 70 71 _plugin.desc = get_descriptor_from_client(client); 72 _plugin.plugin_data = cast(void*)(cast(Object)this); 73 _plugin.init = &plugin_init; 74 _plugin.destroy = &plugin_destroy; 75 _plugin.activate = &plugin_activate; 76 _plugin.deactivate = &plugin_deactivate; 77 _plugin.start_processing = &plugin_start_processing; 78 _plugin.stop_processing = &plugin_stop_processing; 79 _plugin.reset = &plugin_reset; 80 _plugin.process = &plugin_process; 81 _plugin.get_extension = &plugin_get_extension; 82 _plugin.on_main_thread = &plugin_on_main_thread; 83 84 activated = false; 85 processing = false; 86 } 87 88 ~this() 89 { 90 destroyFree(_client); 91 destroyFree(_host); 92 93 for (int i = 0; i < _maxInputs; ++i) 94 _inputBuffers[i].destroy(); 95 for (int i = 0; i < _maxOutputs; ++i) 96 _outputBuffers[i].destroy(); 97 _inputBuffers.freeSlice(); 98 _outputBuffers.freeSlice(); 99 } 100 101 // Resize copy buffers according to maximum block size. 102 void resizeScratchBuffers(int maxFrames, 103 int inputChannels, 104 int outputChannels) 105 { 106 for (int i = 0; i < inputChannels; ++i) 107 _inputBuffers[i].resize(maxFrames); 108 for (int i = 0; i < outputChannels; ++i) 109 _outputBuffers[i].resize(maxFrames); 110 } 111 112 const(clap_plugin_t)* get_clap_plugin() 113 { 114 return &_plugin; 115 } 116 117 private: 118 119 // Underlying generic client. 120 Client _client; 121 122 // Host access. 123 CLAPHost _host; 124 125 // Which DAW is it? 126 DAW _daw = DAW.Unknown; 127 128 // Returned to the CLAP api, it's a sort of v-table. 129 clap_plugin_t _plugin; 130 131 // plugin is "activated" 132 bool activated; 133 134 // plugin is "processing" 135 bool processing; 136 137 // true if resetFromHost must be called before next block 138 bool _mustReset; 139 140 // Last hint at sampleRate, -1 if not specified yet 141 double _sr = -1; 142 143 // Current latency in samples. 144 int _latencySamples = 0; 145 146 // Current tail in samples. int.max if infinite tail. 147 int _tailSamples = int.max; 148 149 // Max frames in block., -1 if not specified yet. 150 int _maxFrames = -1; 151 152 // Max possible number of channels (should belong to Bus ideally) 153 int _maxInputs; 154 155 // Max possible number of channels (should belong to Bus ideally) 156 int _maxOutputs; 157 158 // Input and output scratch buffers, one per channel. 159 Vec!float[] _inputBuffers; 160 Vec!float[] _outputBuffers; 161 162 Vec!(float*) _inputPtrs; 163 Vec!(float*) _outputPtrs; 164 165 // Events that need to be sent to host at next `process`/`flush`. 166 Vec!clap_event_any_t _pendingEvents; 167 168 // Mutex to protect above events. 169 UncheckedMutex _pendingEventsMutex; 170 171 // This parameter is Float and exposed 0 to 1. 172 // This is more correct for float parameter 173 Vec!bool expose_param_as_normalized; 174 175 // Implement methods of clap_plugin_t using the C trampolines 176 177 bool initFun() 178 { 179 _client.setHostCommand(_host); 180 181 // Detect DAW here 182 _daw = _host.getDAW(); 183 184 expose_param_as_normalized.resize(_client.params.length); 185 expose_param_as_normalized.fill(false); 186 187 // Create the bus configuration. 188 _maxInputs = _client.maxInputs(); 189 _maxOutputs = _client.maxOutputs(); 190 bool receivesMIDI = _client.receivesMIDI(); 191 bool sendsMIDI = _client.sendsMIDI(); 192 193 // Note: extrapolate buses from just channel count (: 194 195 if (_maxInputs) 196 { 197 Bus b; 198 b.isMain = true; 199 b.isActive = true; 200 b.name = "Input"; 201 b.numChannels = _maxInputs; 202 audioInputs.pushBack(b); 203 } 204 205 if (_maxOutputs) 206 { 207 Bus b; 208 b.isMain = true; 209 b.isActive = true; 210 b.name = "Output"; 211 b.numChannels = _maxOutputs; 212 audioOutputs.pushBack(b); 213 } 214 215 if (receivesMIDI) 216 { 217 NoteBus b; 218 noteInputs.pushBack(b); 219 } 220 221 if (sendsMIDI) 222 { 223 NoteBus b; 224 noteOutputs.pushBack(b); 225 } 226 227 _inputBuffers = mallocSlice!(Vec!float)(_maxInputs); 228 _outputBuffers = mallocSlice!(Vec!float)(_maxOutputs); 229 return true; 230 } 231 232 // Free the plugin and its resources. 233 // It is required to deactivate the plugin prior to this call. 234 // [main-thread & !active] 235 void destroyFun() 236 { 237 destroyFree(this); 238 } 239 240 bool activate(double sample_rate, 241 uint min_frames_count, 242 uint max_frames_count) 243 { 244 if (max_frames_count > int.max) 245 return false; 246 247 // Note: We can assume we already know the port configuration! 248 // CLAP host are strictly typed and host follow the 249 // constraints. And no synchronization needed, since the 250 // plugin is deactivated. 251 _sr = sample_rate; 252 _maxFrames = assumeNoOverflow(max_frames_count); 253 activated = true; 254 clientReset(); 255 256 // Set latency. Tells the host to check latency immediately. 257 _latencySamples = _client.latencySamples(_sr); 258 _host.notifyLatencyChanged(); 259 260 // Set tail size. 261 float tailSize = _client.tailSizeInSeconds(); 262 assert(tailSize >= 0); 263 if (isinf(tailSize)) 264 { 265 _tailSamples = int.max; 266 } 267 else 268 { 269 long samples = cast(long)(0.5 + tailSize * _sr); 270 if (samples > int.max) 271 samples = int.max; 272 _tailSamples = cast(int)(samples); 273 } 274 _host.notifyTailChanged(); 275 return true; 276 } 277 278 void clientReset() 279 { 280 // We're at a point we know everything about port 281 // configurations. (re)initialize the client. 282 Bus* ibus = getMainInputBus(); 283 Bus* obus = getMainOutputBus(); 284 int numInputs = ibus ? ibus.numChannels : 0; 285 int numOutputs = obus ? obus.numChannels : 0; 286 _client.resetFromHost(_sr, _maxFrames, numInputs, numOutputs); 287 288 // Allocate space for scratch buffers 289 resizeScratchBuffers(_maxFrames, numInputs, numOutputs); 290 _inputPtrs.resize(numInputs); 291 _outputPtrs.resize(numOutputs); 292 } 293 294 void deactivate() 295 { 296 activated = true; 297 } 298 299 bool start_processing() 300 { 301 processing = true; 302 return true; 303 } 304 305 void stop_processing() 306 { 307 processing = false; 308 } 309 310 void reset() 311 { 312 // TBH I don't remember a similar function from other APIs. 313 // Dplug doesn't have that semantic (it's just initialize 314 // + process, no separate reset call) 315 // Since activate can potentially change sample-rate and 316 // allocate, we can simply call our .reset again 317 clientReset(); 318 } 319 320 static struct ParamTrack 321 { 322 nothrow @nogc: 323 Parameter param; 324 int time; 325 double value; // normalized 326 327 bool setIfBetween(int start, int stop) 328 { 329 if (time >= start && time < stop) 330 { 331 param.setFromHost(value); 332 return true; 333 } 334 return false; 335 } 336 } 337 Vec!ParamTrack _tracks; 338 339 clap_process_status process(const(clap_process_t)* pp) 340 { 341 // Split audio buffers and send parameters values to stick to their more. 342 enum bool splitBuffers = false; 343 344 // It seems the number of ports and channels is discovered 345 // here as last resort. 346 347 _tracks.clearContents(); 348 349 // 0. First, process incoming events. 350 if (pp) 351 { 352 bool applyParamsNow = !splitBuffers; 353 if (pp.in_events) 354 processInputEvents(pp.in_events, applyParamsNow); 355 356 // in splitBuffers, _tracks now contain param changes 357 // for this buffer 358 359 processTransportEvent(pp.transport); 360 } 361 362 int inputPorts = pp.audio_inputs_count; 363 int outputPorts = pp.audio_outputs_count; 364 365 if (pp.frames_count > int.min) 366 return CLAP_PROCESS_ERROR; 367 int frames = assumeNoOverflow(pp.frames_count); 368 369 // 1. Check number of buses we agreed upon with the host 370 if (inputPorts != audioInputs.length) 371 return CLAP_PROCESS_ERROR; 372 if (outputPorts != audioOutputs.length) 373 return CLAP_PROCESS_ERROR; 374 375 // 2. Check number of channels we agreed upon with the host 376 for (int n = 0; n < inputPorts; ++n) 377 { 378 int expected = getInputBus(n).numChannels; 379 int got = pp.audio_inputs[n].channel_count; 380 if (got != expected) return CLAP_PROCESS_ERROR; 381 } 382 for (int n = 0; n < outputPorts; ++n) 383 { 384 int expected = getOutputBus(n).numChannels; 385 int got = pp.audio_outputs[n].channel_count; 386 if (got != expected) return CLAP_PROCESS_ERROR; 387 } 388 389 Bus* ibus = getMainInputBus(); 390 Bus* obus = getMainOutputBus(); 391 int numInputs = ibus ? ibus.numChannels : 0; 392 int numOutputs = obus ? obus.numChannels : 0; 393 394 395 // 3. Fetch input audio in input buffers 396 if (numInputs) 397 { 398 int chans = ibus.numChannels; 399 for (int chan = 0; chan < chans; ++chan) 400 { 401 const(float)* src = pp.audio_inputs[0].data32[chan]; 402 float* dest = _inputBuffers[chan].ptr; 403 memcpy(dest, src, float.sizeof * frames); 404 } 405 } 406 407 // 3.b Clear output MIDI message from the queue. 408 if (_client.sendsMIDI) 409 _client.clearAccumulatedOutputMidiMessages(); 410 411 412 413 // 4. Process audio 414 { 415 for (int n = 0; n < numInputs; ++n) 416 _inputPtrs[n] = _inputBuffers[n].ptr; 417 for (int n = 0; n < numOutputs; ++n) 418 _outputPtrs[n] = _outputBuffers[n].ptr; 419 420 int splitMaxFrames = _client.getBufferSplitMaxFrames(); 421 if (splitMaxFrames > 512) splitMaxFrames = 512; 422 423 // See Issue 368, we don't want to set parameters too 424 // much in advance. 425 // https://github.com/AuburnSounds/Dplug/issues/368 426 if (splitMaxFrames == 0) splitMaxFrames = 512; 427 428 if (splitBuffers) 429 { 430 int start = 0; 431 int remain = frames; 432 assert(frames >= 0); 433 434 while (remain > 0) 435 { 436 int count = remain; 437 if (count > splitMaxFrames) 438 count = splitMaxFrames; 439 int stop = start + count; 440 441 // 1. Apply param changes in the future count frames 442 // PERF: partial traversal. This is slower than it should, 443 // since host give us ordered CLAP events (well, it should). 444 foreach(ParamTrack t; _tracks[]) 445 { 446 t.setIfBetween(start, stop); 447 } 448 449 // 2. Process count frames 450 bool doNotSplit = true; 451 _client.processAudioFromHost(_inputPtrs[0..numInputs], 452 _outputPtrs[0..numOutputs], 453 frames, 454 _timeInfo, 455 doNotSplit); 456 for (int n = 0; n < numInputs; ++n) 457 _inputPtrs[n] += count; 458 for (int n = 0; n < numOutputs; ++n) 459 _outputPtrs[n] += count; 460 start += count; 461 _timeInfo.timeInSamples += count; 462 } 463 assert(remain == 0); 464 } 465 else 466 { 467 _client.processAudioFromHost(_inputPtrs[0..numInputs], 468 _outputPtrs[0..numOutputs], 469 frames, 470 _timeInfo); 471 _timeInfo.timeInSamples += frames; 472 } 473 } 474 475 // 5. Copy to output 476 if (numOutputs) 477 { 478 int chans = obus.numChannels; 479 for (int ch = 0; ch < chans; ++ch) 480 { 481 const(clap_audio_buffer_t)* outbuf; 482 outbuf = &pp.audio_outputs[0]; 483 float* dest = cast(float*) outbuf.data32[ch]; 484 const(float)* source= _outputBuffers[ch].ptr; 485 memcpy(dest, source, float.sizeof * frames); 486 } 487 } 488 489 // 6. Lastly, process output events. 490 if (pp) 491 { 492 if (pp.out_events) 493 processOutputEvents(pp.out_events); 494 } 495 496 // Note: CLAP can expose more internal state, such as tail, 497 // process only non-silence etc. It is of course 498 // underdocumented. 499 // However a realistic plug-in will implement silence 500 // detection for the other formats as well. 501 return CLAP_PROCESS_CONTINUE; 502 } 503 504 // aka QueryInterface for the people 505 void* get_extension(const(char)* s) 506 { 507 if (streq(s, "clap.params")) 508 { 509 __gshared clap_plugin_params_t api; 510 api.count = &plugin_params_count; 511 api.get_info = &plugin_params_get_info; 512 api.get_value = &plugin_params_get_value; 513 api.value_to_text = &plugin_params_value_to_text; 514 api.text_to_value = &plugin_params_text_to_value; 515 api.flush = &plugin_params_flush; 516 return &api; 517 } 518 519 if (streq(s, "clap.audio-ports")) 520 { 521 __gshared clap_plugin_audio_ports_t api; 522 api.count = &plugin_audio_ports_count; 523 api.get = &plugin_audio_ports_get; 524 return &api; 525 } 526 527 if (_client.hasGUI() && streq(s, "clap.gui")) 528 { 529 __gshared clap_plugin_gui_t api; 530 api.is_api_supported = &plugin_gui_is_api_supported; 531 api.get_preferred_api = &plugin_gui_get_preferred_api; 532 api.create = &plugin_gui_create; 533 api.destroy = &plugin_gui_destroy; 534 api.set_scale = &plugin_gui_set_scale; 535 api.get_size = &plugin_gui_get_size; 536 api.can_resize = &plugin_gui_can_resize; 537 api.get_resize_hints = &plugin_gui_get_resize_hints; 538 api.adjust_size = &plugin_gui_adjust_size; 539 api.set_size = &plugin_gui_set_size; 540 api.set_parent = &plugin_gui_set_parent; 541 api.set_transient = &plugin_gui_set_transient; 542 api.suggest_title = &plugin_gui_suggest_title; 543 api.show = &plugin_gui_show; 544 api.hide = &plugin_gui_hide; 545 return &api; 546 } 547 548 if (streq(s, "clap.latency")) 549 { 550 __gshared clap_plugin_latency_t api; 551 api.get = &plugin_latency_get; 552 return &api; 553 } 554 555 // Note: nothing in the spec forces the host to save session 556 // using the extension but as plug-in we assume that is the 557 // case, the host MUST use clap.state if present. 558 if (streq(s, "clap.state")) 559 { 560 __gshared clap_plugin_state_t api; 561 api.save = &plugin_state_save; 562 api.load = &plugin_state_load; 563 return &api; 564 } 565 566 if ( streq(s, CLAP_EXT_PRESET_LOAD) 567 || streq(s, CLAP_EXT_PRESET_LOAD_COMPAT) ) 568 { 569 __gshared clap_plugin_preset_load_t api; 570 api.from_location = &plugin_preset_load_from_location; 571 return &api; 572 } 573 574 if ( streq(s, CLAP_EXT_CONFIGURABLE_AUDIO_PORTS) 575 || streq(s, CLAP_EXT_CONFIGURABLE_AUDIO_PORTS_COMPAT) ) 576 { 577 __gshared clap_plugin_configurable_audio_ports_t api; 578 api.can_apply_configuration = &plugin_conf_can_apply_config; 579 api.apply_configuration = &plugin_conf_apply_config; 580 return &api; 581 } 582 583 // Was disabled by default. Seen crash with almost all CLAP 584 // hosts: REAPER, Bitwig, clap-info, clap-validator. 585 // Perhaps our implementation is wrong. 586 bool useAudioPortsConfig = false; 587 588 // clap-validator calls audio-ports-config with bad pointers. 589 // clap-info also crash there. 590 if (_daw == DAW.ClapValidator) useAudioPortsConfig = false; 591 if (_daw == DAW.ClapInfo) useAudioPortsConfig = false; 592 593 if (useAudioPortsConfig) 594 { 595 if (streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0) 596 { 597 __gshared clap_plugin_audio_ports_config_t api; 598 api.count = &plugin_ports_config_count; 599 api.get = &plugin_ports_config_get; 600 api.select = &plugin_ports_config_select; 601 return &api; 602 } 603 604 if ( streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO) 605 || streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT) ) 606 { 607 __gshared clap_plugin_audio_ports_config_info_t api; 608 api.current_config = &plugin_ports_current_config; 609 api.get = &plugin_ports_config_info_get; 610 return &api; 611 } 612 } 613 614 bool hasMIDI = _client.receivesMIDI() || _client.sendsMIDI(); 615 616 if (hasMIDI && streq(s, CLAP_EXT_NOTE_PORTS)) 617 { 618 __gshared clap_plugin_note_ports_t api; 619 api.count = &plugin_note_ports_count; 620 api.get = &plugin_note_ports_get; 621 return &api; 622 } 623 624 // extension not supported 625 return null; 626 } 627 628 // clap.params interface implementation 629 630 uint convertParamIndexToParamID(uint param_index) 631 { 632 return param_index; 633 } 634 635 uint convertParamIDToParamIndex(uint param_id) 636 { 637 return param_id; 638 } 639 640 uint params_count() 641 { 642 return cast(uint) _client.params().length; 643 } 644 645 bool params_get_info(uint param_index, clap_param_info_t* info) 646 { 647 // DPlug note about parameter IDs. 648 // Clap parameters IDs are defined as indexes in uint form. 649 // To have better IDs, would need Dplug support for custom 650 // parameters IDs, that would still be `uint`. Could then have 651 // somekind of map. 652 // I don't see too much value spending time choosing those 653 // identifiers, unfortunately. 654 655 Parameter p = _client.param(param_index); 656 if (!p) 657 return false; 658 659 info.id = convertParamIndexToParamID(param_index); 660 661 int flags = 0; 662 double min, max, def; 663 664 if (BoolParameter bp = cast(BoolParameter)p) 665 { 666 flags |= CLAP_PARAM_IS_STEPPED; 667 min = 0; 668 max = 1; 669 def = bp.defaultValue() ? 1 : 0; 670 } 671 else if (IntegerParameter ip = cast(IntegerParameter)p) 672 { 673 if (EnumParameter ep = cast(EnumParameter)p) 674 flags |= CLAP_PARAM_IS_ENUM; 675 flags |= CLAP_PARAM_IS_STEPPED; // truncate called 676 min = ip.minValue(); 677 max = ip.maxValue(); 678 def = ip.defaultValue(); 679 } 680 else if (FloatParameter fp = cast(FloatParameter)p) 681 { 682 // REAPER doesn't accept parameters that are -inf, but 683 // Dplug does. Here, normalize the float params, which 684 // also improve the display in UI-less plugins since it's 685 // mapped as intended. 686 flags |= 0; 687 expose_param_as_normalized[param_index] = true; 688 min = 0; 689 max = 1.0; 690 def = fp.getNormalizedDefault(); 691 } 692 else 693 assert(false); 694 695 if (p.isAutomatable) flags |= CLAP_PARAM_IS_AUTOMATABLE; 696 697 // Note: all Dplug parameters supposed to requires process. 698 flags |= CLAP_PARAM_REQUIRES_PROCESS; 699 700 info.flags = flags; 701 info.min_value = min; 702 info.max_value = max; 703 info.default_value = def; 704 info.cookie = null;//cast(void*) p; // fast access cookie 705 706 p.toNameN(info.name.ptr, CLAP_NAME_SIZE); 707 708 // "" string for module name, Dplug params are flat 709 info.module_[0] = 0; 710 711 return true; 712 } 713 714 double paramValueForHost(Parameter p, int index) 715 { 716 assert(p); 717 718 if (expose_param_as_normalized[index]) 719 { 720 return p.getNormalized(); 721 } 722 723 if (BoolParameter bp = cast(BoolParameter)p) 724 return bp.value() ? 1.0 : 0.0; 725 else if (IntegerParameter ip = cast(IntegerParameter)p) 726 return ip.value(); 727 else if (FloatParameter fp = cast(FloatParameter)p) 728 return fp.value(); 729 else 730 assert(false); 731 } 732 733 bool params_get_value(clap_id param_id, double *out_value) 734 { 735 uint idx = convertParamIDToParamIndex(param_id); 736 Parameter p = _client.param(idx); 737 if (!p) 738 return false; 739 740 *out_value = paramValueForHost(p, idx); 741 742 assert(!isnan(*out_value)); 743 return true; 744 } 745 746 final double normalizeParamValue(Parameter p, double value) 747 { 748 assert(!isnan(value)); 749 750 double normalized; 751 if (BoolParameter bp = cast(BoolParameter)p) 752 normalized = value; 753 else if (IntegerParameter ip = cast(IntegerParameter)p) 754 normalized = ip.toNormalized(cast(int)value); 755 else if (FloatParameter fp = cast(FloatParameter)p) 756 { 757 normalized = fp.toNormalized(value); 758 } 759 else 760 assert(false); 761 return normalized; 762 } 763 764 // eg: "2.3 kHz" 765 bool params_value_to_text(clap_id param_id, 766 double value, 767 char* out_buffer, 768 uint out_buffer_capacity) 769 { 770 uint idx = convertParamIDToParamIndex(param_id); 771 Parameter p = _client.param(idx); 772 if (!p) 773 return false; 774 775 // 1. Find corresponding normalized value 776 double norm = value; 777 if (!expose_param_as_normalized[idx]) 778 norm = normalizeParamValue(p, value); 779 780 // 2. Find text corresponding to that 781 char[CLAP_NAME_SIZE] str; 782 char[CLAP_NAME_SIZE] label; 783 784 p.stringFromNormalizedValue(norm, str.ptr, CLAP_NAME_SIZE); 785 p.toLabelN(label.ptr, CLAP_NAME_SIZE); 786 if (strlen(label.ptr)) 787 snprintf(out_buffer, out_buffer_capacity, 788 "%s %s", str.ptr, 789 label.ptr); 790 else 791 snprintf(out_buffer, out_buffer_capacity, 792 "%s", str.ptr); 793 return true; 794 } 795 796 bool params_text_to_value(clap_id param_id, 797 const(char)* text, 798 double* out_value) 799 { 800 uint idx = convertParamIDToParamIndex(param_id); 801 Parameter p = _client.param(idx); 802 if (!p) 803 return false; 804 805 size_t len = strlen(text); 806 807 double norm; 808 if (p.normalizedValueFromString(text[0..len], norm)) 809 { 810 if (expose_param_as_normalized[idx]) 811 { 812 *out_value = norm; 813 return true; 814 } 815 816 if (BoolParameter bp = cast(BoolParameter)p) 817 *out_value = norm; 818 else if (IntegerParameter ip = cast(IntegerParameter)p) 819 *out_value = ip.fromNormalized(norm); 820 else if (FloatParameter fp = cast(FloatParameter)p) 821 *out_value = fp.fromNormalized(norm); 822 else 823 assert(false); 824 825 return true; 826 } 827 else 828 return false; 829 } 830 831 void params_flush(const(clap_input_events_t) *in_, 832 const(clap_output_events_t) *out_) 833 { 834 processInputEvents(in_, true); 835 processOutputEvents(out_); 836 } 837 838 void processInputEvents(const(clap_input_events_t) *in_, 839 bool setParametersImmediately) 840 { 841 if (in_ == null) 842 return; 843 844 if (in_.size == null) 845 return; 846 847 // Manage incoming messages from host. 848 uint size = in_.size(in_); 849 850 for (uint n = 0; n < size; ++n) 851 { 852 const(clap_event_header_t)* hdr = in_.get(in_, n); 853 processInputEvent(hdr, setParametersImmediately); 854 } 855 } 856 857 // Process input events. 858 // If setParametersImmediately is true, set the parameters now 859 // else keep them in _tracks for later. 860 void processInputEvent(const(clap_event_header_t)* hdr, 861 bool setParametersImmediately) 862 { 863 if (!hdr) return; 864 if (hdr.space_id != 0) return; 865 int ofs = cast(int)hdr.time; 866 if (ofs < 0) return; 867 868 static ubyte velocity(const(clap_event_note_t)* ev) 869 { 870 bool noteOn = (ev.header.type == CLAP_EVENT_NOTE_ON); 871 double fVelocity = ev.velocity; 872 if (fVelocity < 0) fVelocity = 0; 873 if (fVelocity > 1) fVelocity = 1; 874 ubyte vel = cast(ubyte)(0.5 + 127.0 * fVelocity); 875 876 // "A NOTE_ON with a velocity of 0 is valid and 877 // should not be interpreted as a NOTE_OFF." 878 // => Send MIDI but with velocity 1 in that case. 879 if (noteOn && vel == 0) vel = 1; 880 881 return vel; 882 } 883 884 switch(hdr.type) 885 { 886 case CLAP_EVENT_NOTE_ON: 887 case CLAP_EVENT_NOTE_OFF: 888 { 889 // unused, this dialect disabled 890 auto ev = cast(const(clap_event_note_t)*) hdr; 891 892 ubyte vel = velocity(ev); 893 short chan = ev.channel; 894 if (chan == -1) chan = 0; 895 short key = ev.key; 896 // note sure how "key" can be a wildcard? 897 if (key == -1) 898 break; 899 MidiMessage msg; 900 bool noteOn = ev.header.type == CLAP_EVENT_NOTE_ON; 901 if (noteOn) 902 msg = makeMidiMessageNoteOn(ofs, chan, key, vel); 903 else 904 msg = makeMidiMessageNoteOff(ofs, chan, key); 905 _client.enqueueMIDIFromHost(msg); 906 break; 907 } 908 case CLAP_EVENT_NOTE_CHOKE: 909 //FUTURE 910 break; 911 912 case CLAP_EVENT_NOTE_END: 913 // ignore when coming from input 914 break; 915 916 case CLAP_EVENT_PARAM_VALUE: 917 if (hdr.size < clap_event_param_value_t.sizeof) 918 break; 919 auto ev = cast(const(clap_event_param_value_t)*) hdr; 920 921 int index = convertParamIDToParamIndex(ev.param_id); 922 Parameter param = _client.param(index); 923 if (!param) 924 break; 925 926 // Note: assuming wildcard here. For proper handling, 927 // Dplug would have to maintain values of parameters 928 // for many combination, which is a bit much. 929 930 // Set parameter value 931 double norm = ev.value; 932 if (!expose_param_as_normalized[index]) 933 norm = normalizeParamValue(param, ev.value); 934 935 if (setParametersImmediately) 936 { 937 param.setFromHost(norm); 938 } 939 else 940 { 941 ParamTrack track; 942 track.param = param; 943 track.time = hdr.time; 944 track.value = norm; 945 _tracks.pushBack(track); 946 } 947 break; 948 949 case CLAP_EVENT_PARAM_MOD: 950 // Not supported by our CLAP client. 951 break; 952 case CLAP_EVENT_PARAM_GESTURE_BEGIN: 953 case CLAP_EVENT_PARAM_GESTURE_END: 954 // something to use rather in output 955 // FUTURE: should this "hover" the params in the UI? 956 break; 957 958 case CLAP_EVENT_TRANSPORT: 959 auto ev = cast(const(clap_event_transport_t)*) hdr; 960 processTransportEvent(ev); 961 break; 962 963 case CLAP_EVENT_MIDI: 964 auto ev = cast(const(clap_event_midi_t)*) hdr; 965 966 // Note: port is ignored, Dplug assume one port 967 968 MidiMessage msg = MidiMessage(ofs, 969 ev.data[0], 970 ev.data[1], 971 ev.data[2]); 972 _client.enqueueMIDIFromHost(msg); 973 break; 974 975 case CLAP_EVENT_MIDI_SYSEX: 976 // no support in Dplug 977 break; 978 case CLAP_EVENT_MIDI2: 979 // no support in Dplug 980 break; 981 default: 982 } 983 } 984 985 TimeInfo _timeInfo; 986 987 void processTransportEvent(const(clap_event_transport_t)* ev) 988 { 989 if (ev is null) 990 return; 991 992 if (ev.flags & CLAP_TRANSPORT_HAS_TEMPO) 993 _timeInfo.tempo = ev.tempo; 994 995 if (ev.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE) 996 { 997 long timeSamples = cast(long)(ev.song_pos_seconds * cast(double)_sr); 998 _timeInfo.timeInSamples = timeSamples; 999 } 1000 1001 _timeInfo.hostIsPlaying = (ev.flags & CLAP_TRANSPORT_IS_PLAYING) != 0; 1002 } 1003 1004 void processOutputEvents(const(clap_output_events_t) *out_) 1005 { 1006 _pendingEventsMutex.lockLazy(); 1007 1008 size_t len = _pendingEvents.length; 1009 for (size_t n = 0; n < len; ++n) 1010 { 1011 // Note: no sysex support here. 1012 1013 clap_event_any_t* any = &_pendingEvents[n]; 1014 1015 // Nothing says the parameters will be copied... 1016 // But I don't see what to do if they don't. 1017 clap_event_header_t* hdr = cast(clap_event_header_t*)any; 1018 bool ok = out_.try_push(out_, hdr); 1019 } 1020 _pendingEvents.clearContents(); 1021 _pendingEventsMutex.unlock(); 1022 1023 // Next, enqueue MIDI output messages (if any) 1024 if (_client.sendsMIDI) 1025 { 1026 const(MidiMessage)[] messages; 1027 messages = _client.getAccumulatedOutputMidiMessages(); 1028 foreach(ref msg; messages) 1029 { 1030 clap_event_midi_t ev; 1031 ev.header.size = clap_event_midi_t.sizeof; 1032 ev.header.time = msg.offset(); 1033 ev.header.space_id = 0; 1034 ev.header.type = CLAP_EVENT_MIDI; 1035 ev.header.flags = 0; // not live, automation 1036 ev.port_index = 0; // one MIDI port in Dplug 1037 ev.data[0..3] = 0; 1038 msg.toBytes(ev.data.ptr, 3); 1039 1040 bool ok = out_.try_push(out_, &ev.header); 1041 // ignore if failure 1042 } 1043 } 1044 } 1045 1046 void enqueueParamBeginEdit(Parameter param) 1047 { 1048 clap_event_any_t evt; 1049 with (evt.param_gesture) 1050 { 1051 header.size = clap_event_param_gesture_t.sizeof; 1052 header.time = 0; // ASAP: events from UI 1053 header.space_id = 0; 1054 header.type = CLAP_EVENT_PARAM_GESTURE_BEGIN; 1055 header.flags = 0; // ie. automation, and not live 1056 param_id = convertParamIndexToParamID(param.index); 1057 } 1058 _pendingEventsMutex.lockLazy(); 1059 _pendingEvents.pushBack(evt); 1060 _pendingEventsMutex.unlock(); 1061 } 1062 1063 void enqueueParamEndEdit(Parameter param) 1064 { 1065 clap_event_any_t evt; 1066 with (evt.param_gesture) 1067 { 1068 header.size = clap_event_param_gesture_t.sizeof; 1069 header.time = 0; // ASAP: events from UI 1070 header.space_id = 0; 1071 header.type = CLAP_EVENT_PARAM_GESTURE_END; 1072 header.flags = 0; // ie. automation, and not live 1073 param_id = convertParamIndexToParamID(param.index); 1074 } 1075 _pendingEventsMutex.lockLazy(); 1076 _pendingEvents.pushBack(evt); 1077 _pendingEventsMutex.unlock(); 1078 } 1079 1080 void enqueueParamChange(Parameter param) 1081 { 1082 clap_event_any_t evt; 1083 with (evt.param_value) 1084 { 1085 header.size = clap_event_param_value_t.sizeof; 1086 header.time = 0; // ASAP: events from UI 1087 header.space_id = 0; 1088 header.type = CLAP_EVENT_PARAM_VALUE; 1089 header.flags = 0; // ie. automation, and not live 1090 param_id = convertParamIndexToParamID(param.index); 1091 cookie = null; 1092 note_id = -1; 1093 port_index = -1; 1094 channel = -1; 1095 key = -1; 1096 value = paramValueForHost(param, param.index); 1097 } 1098 _pendingEventsMutex.lockLazy(); 1099 _pendingEvents.pushBack(evt); 1100 _pendingEventsMutex.unlock(); 1101 } 1102 1103 // clap.audio-ports utils 1104 static struct Bus 1105 { 1106 bool isMain; 1107 bool isActive; 1108 string name; 1109 int numChannels; // current channel count 1110 } 1111 Vec!Bus audioInputs; 1112 Vec!Bus audioOutputs; 1113 Bus* getBus(bool is_input, uint index) 1114 { 1115 if (is_input) 1116 { 1117 if (index >= audioInputs.length) return null; 1118 return &audioInputs[index]; 1119 } 1120 else 1121 { 1122 if (index >= audioOutputs.length) return null; 1123 return &audioOutputs[index]; 1124 } 1125 } 1126 Bus* getInputBus(int n) { return getBus(true, n); } 1127 Bus* getOutputBus(int n) { return getBus(false, n); } 1128 Bus* getMainInputBus() { return getBus(true, 0); } 1129 Bus* getMainOutputBus() { return getBus(false, 0); } 1130 1131 uint convertBusIndexToBusID(uint index) { return index; } 1132 uint convertBusIDToBusIndex(uint id) { return id; } 1133 1134 1135 // clap.note-ports utils 1136 static struct NoteBus 1137 { 1138 int dummy; 1139 } 1140 Vec!NoteBus noteInputs; 1141 Vec!NoteBus noteOutputs; 1142 NoteBus* getNoteBus(bool is_input, uint index) 1143 { 1144 if (is_input) 1145 { 1146 if (index >= noteInputs.length) return null; 1147 return ¬eInputs[index]; 1148 } 1149 else 1150 { 1151 if (index >= noteOutputs.length) return null; 1152 return ¬eOutputs[index]; 1153 } 1154 } 1155 1156 1157 // audio-ports impl 1158 1159 uint audio_ports_count(bool is_input) 1160 { 1161 if (is_input) 1162 return cast(uint) audioInputs.length; 1163 else 1164 return cast(uint) audioOutputs.length; 1165 } 1166 1167 bool audio_ports_get(uint index, 1168 bool is_input, 1169 clap_audio_port_info_t *info) 1170 { 1171 Bus* b = getBus(is_input, index); 1172 if (!b) 1173 return false; 1174 1175 info.id = convertBusIndexToBusID(index); 1176 snprintf(info.name.ptr, CLAP_NAME_SIZE, "%.*s", 1177 cast(int)(b.name.length), b.name.ptr); 1178 1179 info.flags = 0; 1180 if (b.isMain) 1181 info.flags |= CLAP_AUDIO_PORT_IS_MAIN; 1182 info.channel_count = b.numChannels; 1183 1184 info.port_type = portTypeChans(b.numChannels); 1185 info.in_place_pair = CLAP_INVALID_ID; 1186 return true; 1187 } 1188 1189 // gui implementation 1190 bool gui_is_api_supported(const(char)*api, bool is_floating) 1191 { 1192 if (is_floating) return false; 1193 version(Windows) 1194 { 1195 return streq(api, CLAP_WINDOW_API_WIN32); 1196 } 1197 else version(OSX) 1198 { 1199 return streq(api, CLAP_WINDOW_API_COCOA); 1200 } 1201 else version(linux) 1202 { 1203 return streq(api, CLAP_WINDOW_API_X11); 1204 } 1205 else 1206 return false; 1207 } 1208 1209 bool gui_get_preferred_api(const(char)** api, bool* is_floating) 1210 { 1211 *is_floating = false; 1212 version(Windows) 1213 { 1214 *api = CLAP_WINDOW_API_WIN32.ptr; 1215 return true; 1216 } 1217 else version(OSX) 1218 { 1219 *api = CLAP_WINDOW_API_COCOA.ptr; 1220 return true; 1221 } 1222 else version(linux) 1223 { 1224 *api = CLAP_WINDOW_API_X11.ptr; 1225 return true; 1226 } 1227 else 1228 return false; 1229 } 1230 1231 GraphicsBackend gui_backend = GraphicsBackend.autodetect; 1232 bool gui_apiWorksInPhysicalPixels = false; 1233 double gui_scale = 1.0; 1234 void* gui_parent_handle = null; 1235 UncheckedMutex _graphicsMutex; 1236 1237 // Note: normally such a mutex is useless, as 1238 // all gui extension function are called from main-thread, 1239 // says CLAP spec. 1240 enum string GraphicsMutexLock = 1241 `_graphicsMutex.lockLazy(); 1242 scope(exit) _graphicsMutex.unlock();`; 1243 1244 bool gui_create(const(char)* api, bool is_floating) 1245 { 1246 mixin(GraphicsMutexLock); 1247 // This doesn't allocate things, we wait for full information 1248 // and will only create the window on first open. 1249 1250 version(Windows) 1251 if (strcmp(api, CLAP_WINDOW_API_WIN32.ptr) == 0) 1252 { 1253 gui_backend = GraphicsBackend.win32; 1254 gui_apiWorksInPhysicalPixels = true; 1255 return true; 1256 } 1257 1258 version(OSX) 1259 if (strcmp(api, CLAP_WINDOW_API_COCOA.ptr) == 0) 1260 { 1261 gui_backend = GraphicsBackend.cocoa; 1262 gui_apiWorksInPhysicalPixels = false; 1263 return true; 1264 } 1265 1266 version(linux) 1267 if (strcmp(api, CLAP_WINDOW_API_X11.ptr) == 0) 1268 { 1269 gui_backend = GraphicsBackend.x11; 1270 gui_apiWorksInPhysicalPixels = true; 1271 return true; 1272 } 1273 return false; 1274 } 1275 1276 void gui_destroy() 1277 { 1278 mixin(GraphicsMutexLock); 1279 _client.closeGUI(); 1280 } 1281 1282 bool gui_set_scale(double scale) 1283 { 1284 mixin(GraphicsMutexLock); 1285 // FUTURE: We currently do nothing with that information. 1286 gui_scale = scale; 1287 return true; 1288 } 1289 1290 bool gui_get_size(uint *width, uint *height) 1291 { 1292 mixin(GraphicsMutexLock); 1293 // FUTURE: physical vs logical? 1294 int widthLogical, heightLogical; 1295 1296 if (!_client.getGUISize(&widthLogical, &heightLogical)) 1297 return false; 1298 1299 if (widthLogical < 0 || heightLogical < 0) 1300 return false; 1301 1302 *width = widthLogical; 1303 *height = heightLogical; 1304 return true; 1305 } 1306 1307 bool gui_can_resize() 1308 { 1309 mixin(GraphicsMutexLock); 1310 return _client.getGraphics().isResizeable(); 1311 } 1312 1313 bool gui_get_resize_hints(clap_gui_resize_hints_t *hints) 1314 { 1315 mixin(GraphicsMutexLock); 1316 IGraphics gr = _client.getGraphics(); 1317 int[2] AR = gr.getPreservedAspectRatio(); 1318 hints.can_resize_horizontally = gr.isResizeableHorizontally(); 1319 hints.can_resize_vertically = gr.isResizeableVertically(); 1320 hints.preserve_aspect_ratio = gr.isAspectRatioPreserved(); 1321 hints.aspect_ratio_width = AR[0]; 1322 hints.aspect_ratio_height = AR[1]; 1323 return true; 1324 } 1325 1326 bool gui_adjust_size(uint *width, uint *height) 1327 { 1328 // FUTURE: physical vs logical? 1329 mixin(GraphicsMutexLock); 1330 IGraphics gr = _client.getGraphics(); 1331 int w = *width; 1332 int h = *height; 1333 if (w < 0 || h < 0) return false; 1334 gr.getMaxSmallerValidSize(&w, &h); 1335 if (w < 0 || h < 0) return false; 1336 *width = w; 1337 *height = h; 1338 return true; 1339 } 1340 1341 bool gui_set_size(uint width, uint height) 1342 { 1343 // FUTURE: physical vs logical? 1344 mixin(GraphicsMutexLock); 1345 IGraphics gr = _client.getGraphics(); 1346 return gr.nativeWindowResize(width, height); 1347 } 1348 1349 bool gui_set_parent(const(clap_window_t)* window) 1350 { 1351 mixin(GraphicsMutexLock); 1352 gui_parent_handle = cast(void*)(window.ptr); 1353 return true; 1354 } 1355 1356 bool gui_set_transient(const(clap_window_t)* window) 1357 { 1358 // no support 1359 return false; 1360 } 1361 1362 void gui_suggest_title(const(char)* title) 1363 { 1364 // ignore 1365 } 1366 1367 bool gui_show() 1368 { 1369 mixin(GraphicsMutexLock); 1370 _client.openGUI(gui_parent_handle, null, gui_backend); 1371 return true; 1372 } 1373 1374 bool gui_hide() 1375 { 1376 mixin(GraphicsMutexLock); 1377 _client.closeGUI(); 1378 return true; 1379 } 1380 1381 // state impl 1382 1383 Vec!ubyte _lastChunkLoad; 1384 1385 // Protect save/load. 1386 // clap-validator calls save() and load() at the same time 1387 UncheckedMutex _stateMutex; 1388 1389 enum string StateMutexLock = 1390 `_stateMutex.lockLazy(); 1391 scope(exit) _stateMutex.unlock();`; 1392 1393 bool state_save(const(clap_ostream_t)* stream) 1394 { 1395 mixin(StateMutexLock); 1396 1397 // PERF: could amortize alloc with 1398 // `appendStateChunkFromCurrentState()` 1399 PresetBank bank = _client.presetBank; 1400 assert(stream); 1401 ubyte[] state = bank.getStateChunkFromCurrentState(); 1402 assert(state); 1403 1404 if (state.length > uint.max) 1405 return false; // absurd long chunk 1406 1407 // write size of chunk 1408 uint size = cast(uint)state.length; 1409 version(LittleEndian) 1410 writeExactly(stream, &size, uint.sizeof); 1411 else 1412 static assert(false); 1413 1414 // write chunk 1415 writeExactly(stream, state.ptr, state.length); 1416 1417 free(state.ptr); 1418 return true; 1419 } 1420 1421 bool state_load(const(clap_istream_t)* stream) 1422 { 1423 mixin(StateMutexLock); 1424 assert(stream); 1425 1426 // read size of chunk 1427 uint size = 0; 1428 if (readExactly(stream, &size, uint.sizeof) != uint.sizeof) 1429 return false; 1430 1431 version(LittleEndian) 1432 {} 1433 else 1434 static assert(false); 1435 1436 // Note sure if we should load chunk with zero size, 1437 // OTOH those chunk probably won't ever exist. 1438 if (size == 0) 1439 return true; 1440 1441 // read chunk 1442 _lastChunkLoad.resize(size); 1443 if (readExactly(stream, _lastChunkLoad.ptr, size) == size) 1444 { 1445 // apply chunk 1446 bool err = false; 1447 1448 _client.presetBank.loadStateChunk(_lastChunkLoad[], &err); 1449 if (err) 1450 return false; 1451 1452 // Ask for param value rescan. 1453 _host.notifyRequestParamRescan(CLAP_PARAM_RESCAN_VALUES); 1454 return true; 1455 } 1456 else 1457 return false; 1458 } 1459 1460 // preset-load impl 1461 1462 bool preset_load_from_location(uint location_kind, 1463 const(char)* location, 1464 const(char)* load_key) 1465 { 1466 int index; 1467 1468 if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN) 1469 goto error; 1470 1471 // Same remark as for indexing: MultiTrackStudio sends 1472 // non-null location, ignore that. 1473 1474 if (sscanf(load_key, "%d", &index) != 1) 1475 goto error; 1476 if (index < 0 || index > _client.presetBank.numPresets) 1477 goto error; 1478 1479 _client.presetBank.loadPresetFromHost(index); 1480 1481 _host.notifyPresetLoaded(location_kind, 1482 location, 1483 load_key); 1484 // Ask for param value rescan. 1485 _host.notifyRequestParamRescan(CLAP_PARAM_RESCAN_VALUES); 1486 return true; 1487 1488 error: 1489 _host.notifyPresetError(location_kind, 1490 location, 1491 load_key, 1492 0, 1493 "Couldn't load preset"); 1494 return false; 1495 } 1496 1497 1498 // audio-ports-config impl 1499 1500 uint ports_config_count() 1501 { 1502 LegalIO[] legalIOs = _client.legalIOs(); 1503 return cast(uint) (legalIOs.length); 1504 } 1505 1506 bool ports_config_get(uint index, 1507 clap_audio_ports_config_t* config) 1508 { 1509 LegalIO[] legalIOs = _client.legalIOs(); 1510 if (index >= legalIOs.length) 1511 return false; 1512 1513 LegalIO* io = &legalIOs[index]; 1514 config.id = index; 1515 // Call that "2-2" for stereo, etc 1516 snprintf(config.name.ptr, CLAP_NAME_SIZE, "%d-%d", 1517 io.numInputChannels, io.numOutputChannels); 1518 int inChannels = io.numInputChannels; 1519 int outChannels = io.numOutputChannels; 1520 config.input_port_count = inChannels ? 1 : 0; 1521 config.output_port_count = outChannels ? 1 : 0; 1522 config.has_main_input = (inChannels != 0); 1523 config.has_main_output = (outChannels != 0); 1524 config.main_input_channel_count = inChannels; 1525 config.main_output_channel_count = outChannels; 1526 config.main_input_port_type = portTypeChans(inChannels); 1527 config.main_output_port_type = portTypeChans(outChannels); 1528 return true; 1529 } 1530 1531 static const(char)* portTypeChans(int channels) pure 1532 { 1533 switch(channels) 1534 { 1535 case 1: return CLAP_PORT_MONO.ptr; 1536 case 2: return CLAP_PORT_STEREO.ptr; 1537 default: 1538 // no support yet for ambisonic or surround 1539 return null; 1540 } 1541 } 1542 1543 bool ports_config_select(clap_id config_id) 1544 { 1545 LegalIO[] legalIOs = _client.legalIOs(); 1546 uint index = config_id; 1547 if (index >= legalIOs.length) 1548 return false; 1549 1550 LegalIO* io = &legalIOs[index]; 1551 Bus* mainIn = getMainInputBus(); 1552 Bus* mainOut = getMainOutputBus(); 1553 1554 // FUTURE: this would set the number of channels in SC too 1555 if (mainIn) mainIn.numChannels = io.numInputChannels; 1556 if (mainOut) mainOut.numChannels = io.numOutputChannels; 1557 return true; 1558 } 1559 1560 clap_id ports_current_config() 1561 { 1562 LegalIO[] legalIOs = _client.legalIOs(); 1563 Bus* mainIn = getMainInputBus(); 1564 Bus* mainOut = getMainOutputBus(); 1565 int inChannels = 0; 1566 int outChannels = 0; 1567 if (mainIn) inChannels = mainIn.numChannels; 1568 if (mainOut) outChannels = mainOut.numChannels; 1569 foreach(size_t nth, ref io; legalIOs) 1570 { 1571 if ( (io.numInputChannels == inChannels) 1572 && (io.numOutputChannels == outChannels) ) 1573 return cast(clap_id)nth; 1574 } 1575 return CLAP_INVALID_ID; 1576 } 1577 1578 bool ports_config_info_get(clap_id config_id, 1579 uint port_index, 1580 bool is_input, 1581 clap_audio_port_info_t *info) 1582 { 1583 LegalIO[] legalIOs = _client.legalIOs(); 1584 uint index = config_id; 1585 if (index >= legalIOs.length) 1586 return false; 1587 LegalIO* io = &legalIOs[index]; 1588 Bus* b = getBus(is_input, port_index); 1589 if (!b) 1590 return false; 1591 1592 info.id = convertBusIndexToBusID(port_index); 1593 snprintf(info.name.ptr, CLAP_NAME_SIZE, "%.*s", 1594 cast(int)(b.name.length), b.name.ptr); 1595 1596 info.flags = 0; 1597 if (b.isMain) 1598 info.flags |= CLAP_AUDIO_PORT_IS_MAIN; 1599 info.channel_count = is_input ? io.numInputChannels 1600 : io.numOutputChannels; 1601 1602 info.port_type = portTypeChans(info.channel_count); 1603 1604 // True luxury in life is moments like that, letting the host 1605 // deal with that at last. 1606 info.in_place_pair = CLAP_INVALID_ID; 1607 return true; 1608 } 1609 1610 // note-ports impl 1611 uint note_ports_count(bool is_input) 1612 { 1613 if (is_input) 1614 return cast(uint) noteInputs.length; 1615 else 1616 return cast(uint) noteOutputs.length; 1617 } 1618 1619 bool note_ports_get(uint index, 1620 bool is_input, 1621 clap_note_port_info_t *info) 1622 { 1623 NoteBus* bus = getNoteBus(is_input, index); 1624 if (!bus) 1625 return false; 1626 with(info) 1627 { 1628 id = convertBusIndexToBusID(index); 1629 supported_dialects = CLAP_NOTE_DIALECT_MIDI; 1630 1631 // disabled since bizarre semantics I'm unsure of 1632 // and no time to test that 1633 //supported_dialects |= CLAP_NOTE_DIALECT_CLAP; 1634 1635 preferred_dialect = CLAP_NOTE_DIALECT_MIDI; 1636 snprintf(name.ptr, CLAP_NAME_SIZE, "Events"); 1637 } 1638 return true; 1639 } 1640 1641 // configurable audio ports 1642 1643 bool conf_can_apply_config( 1644 const(clap_audio_port_configuration_request_t)* requests, 1645 uint request_count) 1646 { 1647 int ioIndex = matchLegalIO(requests, request_count); 1648 if (ioIndex == -1) 1649 return false; 1650 1651 // Yes, we found a legalIO that can do that. 1652 return true; 1653 } 1654 1655 bool conf_apply_config( 1656 const(clap_audio_port_configuration_request_t)* requests, 1657 uint request_count) 1658 { 1659 int ioIndex = matchLegalIO(requests, request_count); 1660 if (ioIndex == -1) 1661 return false; 1662 1663 // Note: legalIOs index are same as config clap_id 1664 return ports_config_select(ioIndex); 1665 } 1666 1667 int matchLegalIO( 1668 const(clap_audio_port_configuration_request_t)* requests, 1669 uint request_count) 1670 { 1671 LegalIO[] legalIOs = _client.legalIOs(); 1672 int bestIndex = -1; 1673 int bestScore = -1; 1674 1675 foreach(size_t index, io; legalIOs) 1676 { 1677 int score = 0; 1678 1679 // match each of the requests with && 1680 1681 for (uint n = 0; n < request_count; ++n) 1682 { 1683 auto r = &requests[n]; 1684 1685 // Does that port exist? 1686 Bus* b = getBus(r.is_input, r.port_index); 1687 if (!b) 1688 { 1689 if (r.channel_count != 0) 1690 { 1691 score = -1; // fail, expected some channels 1692 break; 1693 } 1694 else 1695 continue; // bus is matching that zero chan 1696 } 1697 1698 if (r.port_index != 0) 1699 { 1700 score = -1; // fail, no support for multiple ports 1701 break; 1702 } 1703 1704 int chan = r.is_input ? io.numInputChannels : io.numOutputChannels; 1705 if (chan == r.channel_count) 1706 { 1707 // good number of channel 1708 score += 1; 1709 } 1710 1711 // Note: ignoring port_type or port_details here 1712 } 1713 1714 if (score > bestScore) 1715 { 1716 bestIndex = cast(int) index; 1717 bestScore = score; 1718 } 1719 } 1720 1721 return bestIndex; // return choosen legalIO 1722 } 1723 } 1724 1725 extern(C) static 1726 { 1727 enum string ClientCallback = 1728 `ScopedForeignCallback!(false, true) sc; 1729 sc.enter(); 1730 CLAPClient client = cast(CLAPClient)(plugin.plugin_data);`; 1731 1732 // plugin callbacks 1733 1734 bool plugin_init(const(clap_plugin_t)* plugin) 1735 { 1736 mixin(ClientCallback); 1737 return client.initFun(); 1738 } 1739 1740 void plugin_destroy(const(clap_plugin_t)* plugin) 1741 { 1742 mixin(ClientCallback); 1743 client.destroyFun(); 1744 } 1745 1746 bool plugin_activate(const(clap_plugin_t)* plugin, 1747 double sample_rate, 1748 uint min_frames_count, 1749 uint max_frames_count) 1750 { 1751 mixin(ClientCallback); 1752 return client.activate(sample_rate, 1753 min_frames_count, 1754 max_frames_count); 1755 } 1756 1757 void plugin_deactivate(const(clap_plugin_t)*plugin) 1758 { 1759 mixin(ClientCallback); 1760 return client.deactivate(); 1761 } 1762 1763 bool plugin_start_processing(const(clap_plugin_t)*plugin) 1764 { 1765 mixin(ClientCallback); 1766 return client.start_processing(); 1767 } 1768 1769 void plugin_stop_processing(const(clap_plugin_t)*plugin) 1770 { 1771 mixin(ClientCallback); 1772 client.stop_processing(); 1773 } 1774 1775 void plugin_reset(const(clap_plugin_t)*plugin) 1776 { 1777 mixin(ClientCallback); 1778 client.reset(); 1779 } 1780 1781 clap_process_status plugin_process(const(clap_plugin_t)* plugin, 1782 const(clap_process_t)* pp) 1783 { 1784 mixin(ClientCallback); 1785 return client.process(pp); 1786 } 1787 1788 const(void)* plugin_get_extension(const(clap_plugin_t)* plugin, 1789 const(char)* id) 1790 { 1791 mixin(ClientCallback); 1792 return client.get_extension(id); 1793 } 1794 1795 void plugin_on_main_thread(const(clap_plugin_t)*plugin) 1796 { 1797 // do nothing here 1798 } 1799 1800 1801 // clap.params callbacks 1802 1803 uint plugin_params_count(const(clap_plugin_t)*plugin) 1804 { 1805 mixin(ClientCallback); 1806 return client.params_count(); 1807 } 1808 1809 bool plugin_params_get_info(const(clap_plugin_t)* plugin, 1810 uint param_index, 1811 clap_param_info_t* param_info) 1812 { 1813 mixin(ClientCallback); 1814 return client.params_get_info(param_index, param_info); 1815 } 1816 1817 bool plugin_params_get_value(const(clap_plugin_t)*plugin, 1818 clap_id param_id, 1819 double* out_value) 1820 { 1821 mixin(ClientCallback); 1822 return client.params_get_value(param_id, out_value); 1823 } 1824 1825 // eg: "2.3 kHz" 1826 bool plugin_params_value_to_text(const(clap_plugin_t)* plugin, 1827 clap_id param_id, 1828 double value, 1829 char* out_buffer, 1830 uint out_buffer_capacity) 1831 { 1832 mixin(ClientCallback); 1833 return client.params_value_to_text(param_id, value, 1834 out_buffer, out_buffer_capacity); 1835 } 1836 1837 bool plugin_params_text_to_value(const(clap_plugin_t)* plugin, 1838 clap_id param_id, 1839 const(char)* param_value_text, 1840 double* out_value) 1841 { 1842 mixin(ClientCallback); 1843 return client.params_text_to_value(param_id, 1844 param_value_text, out_value); 1845 } 1846 1847 void plugin_params_flush(const(clap_plugin_t)* plugin, 1848 const(clap_input_events_t)* in_, 1849 const(clap_output_events_t)* out_) 1850 { 1851 mixin(ClientCallback); 1852 return client.params_flush(in_, out_); 1853 } 1854 1855 uint plugin_audio_ports_count(const(clap_plugin_t)* plugin, 1856 bool is_input) 1857 { 1858 mixin(ClientCallback); 1859 return client.audio_ports_count(is_input); 1860 } 1861 1862 bool plugin_audio_ports_get(const(clap_plugin_t)* plugin, 1863 uint index, 1864 bool is_input, 1865 clap_audio_port_info_t *info) 1866 { 1867 mixin(ClientCallback); 1868 return client.audio_ports_get(index, is_input, info); 1869 } 1870 1871 1872 // gui callbacks 1873 1874 bool plugin_gui_is_api_supported(const(clap_plugin_t)* plugin, 1875 const(char)* api, 1876 bool is_floating) 1877 { 1878 mixin(ClientCallback); 1879 return client.gui_is_api_supported(api, is_floating); 1880 } 1881 1882 bool plugin_gui_get_preferred_api(const(clap_plugin_t)* plugin, 1883 const(char)** api, 1884 bool* is_floating) 1885 { 1886 mixin(ClientCallback); 1887 return client.gui_get_preferred_api(api, is_floating); 1888 } 1889 1890 bool plugin_gui_create(const(clap_plugin_t)* plugin, 1891 const(char)* api, 1892 bool is_floating) 1893 { 1894 mixin(ClientCallback); 1895 return client.gui_create(api, is_floating); 1896 } 1897 1898 void plugin_gui_destroy(const(clap_plugin_t)* plugin) 1899 { 1900 mixin(ClientCallback); 1901 return client.gui_destroy(); 1902 } 1903 1904 bool plugin_gui_set_scale(const(clap_plugin_t)* plugin, double s) 1905 { 1906 mixin(ClientCallback); 1907 return client.gui_set_scale(s); 1908 } 1909 1910 bool plugin_gui_get_size(const(clap_plugin_t)* plugin, 1911 uint* width, 1912 uint* height) 1913 { 1914 mixin(ClientCallback); 1915 return client.gui_get_size(width, height); 1916 } 1917 1918 bool plugin_gui_can_resize(const(clap_plugin_t)* plugin) 1919 { 1920 mixin(ClientCallback); 1921 return client.gui_can_resize(); 1922 } 1923 1924 bool plugin_gui_get_resize_hints(const(clap_plugin_t)* plugin, 1925 clap_gui_resize_hints_t* hints) 1926 { 1927 mixin(ClientCallback); 1928 return client.gui_get_resize_hints(hints); 1929 } 1930 1931 bool plugin_gui_adjust_size(const(clap_plugin_t)* plugin, 1932 uint* width, 1933 uint* height) 1934 { 1935 mixin(ClientCallback); 1936 return client.gui_adjust_size(width, height); 1937 } 1938 1939 bool plugin_gui_set_size(const(clap_plugin_t)* plugin, 1940 uint width, 1941 uint height) 1942 { 1943 mixin(ClientCallback); 1944 return client.gui_set_size(width, height); 1945 } 1946 1947 bool plugin_gui_set_parent(const(clap_plugin_t)* plugin, 1948 const(clap_window_t)* window) 1949 { 1950 mixin(ClientCallback); 1951 return client.gui_set_parent(window); 1952 } 1953 1954 bool plugin_gui_set_transient(const(clap_plugin_t)* plugin, 1955 const(clap_window_t)* window) 1956 { 1957 mixin(ClientCallback); 1958 return client.gui_set_transient(window); 1959 } 1960 1961 void plugin_gui_suggest_title(const(clap_plugin_t)* plugin, 1962 const(char)* title) 1963 { 1964 mixin(ClientCallback); 1965 return client.gui_suggest_title(title); 1966 } 1967 1968 bool plugin_gui_show(const(clap_plugin_t)* plugin) 1969 { 1970 mixin(ClientCallback); 1971 return client.gui_show(); 1972 } 1973 1974 bool plugin_gui_hide(const(clap_plugin_t)* plugin) 1975 { 1976 mixin(ClientCallback); 1977 return client.gui_hide(); 1978 } 1979 1980 // latency callbacks 1981 uint plugin_latency_get(const(clap_plugin_t)* plugin) 1982 { 1983 mixin(ClientCallback); 1984 int samples = client._latencySamples; 1985 assert(samples >= 0); 1986 return samples; 1987 } 1988 1989 // tail callbacks 1990 uint plugin_tail_get(const(clap_plugin_t)* plugin) 1991 { 1992 mixin(ClientCallback); 1993 int samples = client._tailSamples; 1994 assert(samples >= 0); 1995 return samples; 1996 } 1997 1998 // state callbacks 1999 2000 bool plugin_state_save(const(clap_plugin_t)* plugin, 2001 const(clap_ostream_t)* stream) 2002 { 2003 mixin(ClientCallback); 2004 return client.state_save(stream); 2005 } 2006 2007 bool plugin_state_load(const(clap_plugin_t)* plugin, 2008 const(clap_istream_t)* stream) 2009 { 2010 mixin(ClientCallback); 2011 return client.state_load(stream); 2012 } 2013 2014 // preset-load impl 2015 bool plugin_preset_load_from_location( 2016 const(clap_plugin_t)*plugin, 2017 uint location_kind, 2018 const(char) *location, 2019 const(char) *load_key) 2020 { 2021 mixin(ClientCallback); 2022 return client.preset_load_from_location(location_kind, 2023 location, 2024 load_key); 2025 } 2026 2027 // audio-port-config callbacks 2028 2029 uint plugin_ports_config_count(const(clap_plugin_t)* plugin) 2030 { 2031 mixin(ClientCallback); 2032 return client.ports_config_count(); 2033 } 2034 2035 bool plugin_ports_config_get(const(clap_plugin_t)* plugin, 2036 uint index, 2037 clap_audio_ports_config_t* config) 2038 { 2039 mixin(ClientCallback); 2040 return client.ports_config_get(index, config); 2041 } 2042 2043 bool plugin_ports_config_select(const(clap_plugin_t)* plugin, 2044 clap_id config_id) 2045 { 2046 mixin(ClientCallback); 2047 return client.ports_config_select(config_id); 2048 } 2049 2050 // clap_plugin_audio_ports_config_info_t 2051 2052 clap_id plugin_ports_current_config(const(clap_plugin_t)*plugin) 2053 { 2054 mixin(ClientCallback); 2055 return client.ports_current_config(); 2056 } 2057 2058 bool plugin_ports_config_info_get(const(clap_plugin_t)*plugin, 2059 clap_id config_id, 2060 uint port_index, 2061 bool is_input, 2062 clap_audio_port_info_t *info) 2063 { 2064 mixin(ClientCallback); 2065 return client.ports_config_info_get(config_id, port_index, 2066 is_input, info); 2067 } 2068 2069 // note-ports callbacks 2070 2071 uint plugin_note_ports_count(const(clap_plugin_t)* plugin, 2072 bool is_input) 2073 { 2074 mixin(ClientCallback); 2075 return client.note_ports_count(is_input); 2076 } 2077 2078 // Get info about a note port. 2079 // Returns true on success and stores the result into info. 2080 // [main-thread] 2081 bool plugin_note_ports_get(const(clap_plugin_t)* plugin, 2082 uint index, 2083 bool is_input, 2084 clap_note_port_info_t* info) 2085 { 2086 mixin(ClientCallback); 2087 return client.note_ports_get(index, is_input, info); 2088 } 2089 2090 // configurable-audio-ports callbacks 2091 2092 bool plugin_conf_can_apply_config( 2093 const(clap_plugin_t)* plugin, 2094 const(clap_audio_port_configuration_request_t)* requests, 2095 uint request_count) 2096 { 2097 mixin(ClientCallback); 2098 return client.conf_can_apply_config(requests, 2099 request_count); 2100 } 2101 2102 bool plugin_conf_apply_config( 2103 const(clap_plugin_t)* plugin, 2104 const(clap_audio_port_configuration_request_t)* requests, 2105 uint request_count) 2106 { 2107 mixin(ClientCallback); 2108 return client.conf_apply_config(requests, 2109 request_count); 2110 } 2111 } 2112 2113 2114 // CLAP host commands 2115 2116 class CLAPHost : IHostCommand 2117 { 2118 nothrow @nogc: 2119 this(CLAPClient backRef, const(clap_host_t)* host) 2120 { 2121 _backRef = backRef; 2122 _host = host; 2123 _host_gui = cast(clap_host_gui_t*) 2124 host.get_extension(host, "clap.gui".ptr); 2125 _host_latency = cast(clap_host_latency_t*) 2126 host.get_extension(host, "clap.latency".ptr); 2127 _host_params = cast(clap_host_params_t*) 2128 host.get_extension(host, "clap.params".ptr); 2129 _host_tail = cast(clap_host_tail_t*) 2130 host.get_extension(host, "clap.tail".ptr); 2131 _host_preset = cast(clap_host_preset_load_t*) 2132 host.get_extension(host, CLAP_EXT_PRESET_LOAD.ptr); 2133 if (_host_preset) 2134 return; 2135 _host_preset = cast(clap_host_preset_load_t*) 2136 host.get_extension(host, CLAP_EXT_PRESET_LOAD_COMPAT.ptr); 2137 } 2138 2139 /// Notifies the host that editing of a parameter has begun from 2140 /// UI side. 2141 override void beginParamEdit(int paramIndex) 2142 { 2143 Parameter p = _backRef._client.param(paramIndex); 2144 if (!p) 2145 return; 2146 _backRef.enqueueParamBeginEdit(p); 2147 notifyRequestFlush(); 2148 } 2149 2150 /// Notifies the host that a parameter was edited from the UI side. 2151 /// This enables the host to record automation. 2152 /// It is illegal to call `paramAutomate` outside of a 2153 /// `beginParamEdit`/`endParamEdit` pair. 2154 override void paramAutomate(int paramIndex, float value) 2155 { 2156 Parameter p = _backRef._client.param(paramIndex); 2157 if (!p) 2158 return; 2159 _backRef.enqueueParamChange(p); 2160 notifyRequestFlush(); 2161 } 2162 2163 /// Notifies the host that editing of a parameter has finished 2164 /// from UI side. 2165 override void endParamEdit(int paramIndex) 2166 { 2167 Parameter p = _backRef._client.param(paramIndex); 2168 if (!p) 2169 return; 2170 _backRef.enqueueParamEndEdit(p); 2171 notifyRequestFlush(); 2172 } 2173 2174 /// Requests to the host a resize of the plugin window's PARENT 2175 /// window, given logical pixels of plugin window. 2176 /// 2177 /// Note: UI widgets and plugin format clients have different 2178 /// coordinate systems. 2179 /// 2180 /// Params: 2181 /// width New width of the plugin, in logical pixels. 2182 /// height New height of the plugin, in logical pixels. 2183 /// Returns: `true` if the host parent window has been resized. 2184 override bool requestResize(int widthLogicalPixels, 2185 int heightLogicalPixels) 2186 { 2187 if (!_host_gui) 2188 return false; 2189 if (widthLogicalPixels < 0 || heightLogicalPixels < 0) 2190 return false; 2191 return _host_gui.request_resize(_host, 2192 widthLogicalPixels, 2193 heightLogicalPixels); 2194 } 2195 2196 override bool notifyResized() 2197 { 2198 return false; 2199 } 2200 2201 void notifyRequestFlush() 2202 { 2203 // says to the host to call flush or process, so that input 2204 // and output events can be processed 2205 if (_host_params) 2206 _host_params.request_flush(_host); 2207 } 2208 2209 void notifyRequestParamRescan(clap_param_rescan_flags flags) 2210 { 2211 // says to the host to rescan parameters 2212 if (_host_params) 2213 _host_params.rescan(_host, flags); 2214 } 2215 2216 // Tell the host the latency changed while activated. 2217 bool notifyLatencyChanged() 2218 { 2219 if (_host_latency) 2220 { 2221 _host_latency.changed(_host); 2222 return true; 2223 } 2224 else 2225 return false; 2226 } 2227 2228 // Tell the host the tail size changed. 2229 bool notifyTailChanged() 2230 { 2231 if (_host_tail) 2232 { 2233 _host_tail.changed(_host); 2234 return true; 2235 } 2236 else 2237 return false; 2238 } 2239 2240 void notifyPresetLoaded(uint location_kind, 2241 const(char) *location, 2242 const(char) *load_key) 2243 { 2244 if (_host_preset) 2245 _host_preset.loaded(_host, 2246 location_kind, 2247 location, 2248 load_key); 2249 } 2250 2251 void notifyPresetError(uint location_kind, 2252 const(char) *location, 2253 const(char) *load_key, 2254 int os_error, 2255 const(char)* msg) 2256 { 2257 if (_host_preset) 2258 _host_preset.on_error(_host, 2259 location_kind, 2260 location, 2261 load_key, 2262 os_error, 2263 msg); 2264 } 2265 2266 DAW getDAW() 2267 { 2268 char[128] dawStr; 2269 snprintf(dawStr.ptr, 128, "%s", _host.name); 2270 2271 // Force lowercase 2272 for (char* p = dawStr.ptr; *p != '\0'; ++p) 2273 { 2274 if (*p >= 'A' && *p <= 'Z') 2275 *p += ('a' - 'A'); 2276 } 2277 2278 return identifyDAW(dawStr.ptr); 2279 } 2280 2281 PluginFormat getPluginFormat() 2282 { 2283 return PluginFormat.clap; 2284 } 2285 2286 CLAPClient _backRef; 2287 const(clap_host_t)* _host; 2288 const(clap_host_gui_t)* _host_gui; 2289 const(clap_host_latency_t)* _host_latency; 2290 const(clap_host_params_t)* _host_params; 2291 const(clap_host_tail_t)* _host_tail; 2292 const(clap_host_preset_load_t)* _host_preset; 2293 } 2294 2295 class CLAPPresetProvider 2296 { 2297 public: 2298 nothrow: 2299 @nogc: 2300 2301 this(Client client, const(clap_preset_discovery_indexer_t)* idxer) 2302 { 2303 _indexer = idxer; 2304 _client = client; 2305 } 2306 2307 ~this() 2308 { 2309 destroyFree(_client); 2310 } 2311 2312 UncheckedMutex _presetMutex; 2313 __gshared clap_preset_discovery_filetype_t filetype; 2314 __gshared clap_universal_plugin_id_t thisPlugin; 2315 2316 bool init_() 2317 { 2318 _presetMutex.lockLazy(); 2319 scope(exit) _presetMutex.unlock(); 2320 2321 clap_preset_discovery_location_t loc; 2322 loc.flags = CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT; 2323 loc.name = "Factory presets"; 2324 loc.kind = CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN; 2325 loc.location = null; 2326 2327 thisPlugin.abi = "clap"; 2328 thisPlugin.id = assumeZeroTerminated(_client.CLAPIdentifier); 2329 2330 // Note: this file extension makes no sense but CLAP 2331 // apparently forces us to declare one. 2332 filetype.name = "Dplug CLAP chunk"; 2333 filetype.description = "Dplug factory preset format"; 2334 filetype.file_extension = "patch"; 2335 2336 // FUTURE: rare error ignored here 2337 bool res = _indexer.declare_filetype(_indexer, &filetype); 2338 res = _indexer.declare_location(_indexer, &loc); 2339 return true; 2340 } 2341 2342 bool get_metadata( 2343 uint location_kind, 2344 const(char)* location, 2345 const(clap_preset_discovery_metadata_receiver_t)* m) 2346 { 2347 _presetMutex.lockLazy(); 2348 scope(exit) _presetMutex.unlock(); 2349 2350 if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN) 2351 return false; 2352 2353 // Here is a trick, multi-track studio when given a NULL 2354 // location, will get back to us with a "" location, and a 2355 // non-null pointer. Do NOT check location. 2356 2357 PresetBank bank = _client.presetBank(); 2358 int numPresets = bank.numPresets(); 2359 2360 for (int n = 0; n < numPresets; ++n) 2361 { 2362 Preset preset = bank.preset(n); 2363 2364 // Assuming the load key is copied here. 2365 // Load key is simply the preset index. 2366 char[24] load_key; 2367 snprintf(load_key.ptr, 24, "%d", n); 2368 const(char)* nameZ = assumeZeroTerminated(preset.name); 2369 if (!m.begin_preset(m, nameZ, load_key.ptr)) 2370 break; 2371 2372 // say this preset is for that plugin 2373 m.add_plugin_id(m, &thisPlugin); 2374 m.set_flags(m, CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT); 2375 } 2376 return true; 2377 } 2378 2379 private: 2380 const(clap_preset_discovery_indexer_t)* _indexer; 2381 Client _client; 2382 } 2383 2384 extern(C) static 2385 { 2386 enum string PresetCallback = 2387 `ScopedForeignCallback!(false, true) sc; 2388 sc.enter(); 2389 CLAPPresetProvider provobj = cast(CLAPPresetProvider) 2390 cast(Object)(provider.provider_data);`; 2391 2392 alias clap_preset_dp_t = clap_preset_discovery_provider_t; 2393 2394 // plugin callbacks 2395 2396 bool provider_init(const(clap_preset_dp_t)* provider) 2397 { 2398 mixin(PresetCallback); 2399 return provobj.init_(); 2400 } 2401 2402 void provider_destroy(const(clap_preset_dp_t)* provider) 2403 { 2404 mixin(PresetCallback); 2405 destroyFree(provobj); 2406 } 2407 2408 bool provider_get_metadata( 2409 const(clap_preset_dp_t)* provider, 2410 uint location_kind, 2411 const(char)* location, 2412 const(clap_preset_discovery_metadata_receiver_t)* mr) 2413 { 2414 mixin(PresetCallback); 2415 return provobj.get_metadata(location_kind, location, mr); 2416 } 2417 2418 const(void)* provider_get_extension( 2419 const(clap_preset_dp_t)* provider, 2420 const(char)* extension_id) 2421 { 2422 return null; 2423 } 2424 }