1 /* 2 Cockos WDL License 3 4 Copyright (C) 2005 - 2015 Cockos Incorporated 5 Copyright (C) 2015 - 2017 Auburn Sounds 6 7 Portions copyright other contributors, see each source file for more information 8 9 This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 10 11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 15 1. This notice may not be removed or altered from any source distribution. 16 */ 17 18 /// Base client implementation. Every plugin format implementation hold a `Client` member. 19 module dplug.client.client; 20 21 import core.atomic; 22 import core.stdc.string; 23 import core.stdc.stdio; 24 import core.stdc.stdlib: free; 25 26 import dplug.core.nogc; 27 import dplug.core.math; 28 import dplug.core.vec; 29 import dplug.core.sync; 30 31 import dplug.client.params; 32 import dplug.client.preset; 33 import dplug.client.midi; 34 import dplug.client.graphics; 35 import dplug.client.daw; 36 37 38 enum PluginFormat 39 { 40 vst2, 41 vst3, 42 aax, 43 auv2, 44 lv2 45 } 46 47 48 /// A plugin client can send commands to the host. 49 /// This interface is injected after the client creation though. 50 interface IHostCommand 51 { 52 nothrow @nogc: 53 54 /// Notifies the host that editing of a parameter has begun from UI side. 55 void beginParamEdit(int paramIndex); 56 57 /// Notifies the host that a parameter was edited from the UI side. 58 /// This enables the host to record automation. 59 /// It is illegal to call `paramAutomate` outside of a `beginParamEdit`/`endParamEdit` pair. 60 void paramAutomate(int paramIndex, float value); 61 62 /// Notifies the host that editing of a parameter has finished from UI side. 63 void endParamEdit(int paramIndex); 64 65 /// Requests to the host a resize of the plugin window's PARENT window, given logical pixels of plugin window. 66 /// 67 /// Note: UI widgets and plugin format clients have different coordinate systems. 68 /// 69 /// Params: 70 /// width New width of the plugin, in logical pixels. 71 /// height New height of the plugin, in logical pixels. 72 /// Returns: `true` if the host parent window has been resized. 73 bool requestResize(int widthLogicalPixels, int heightLogicalPixels); 74 75 /// Report the identied host name (DAW). 76 /// MAYDO: not available for LV2. 77 DAW getDAW(); 78 79 /// Gets the plugin format used at runtime. Version identifier may not be enough in the future, in case of 80 /// unity builds. 81 PluginFormat getPluginFormat(); 82 } 83 84 // Plugin version in major.minor.patch form. 85 struct PluginVersion 86 { 87 nothrow: 88 @nogc: 89 int major; 90 int minor; 91 int patch; 92 93 int toVSTVersion() pure const 94 { 95 assert(major < 10 && minor < 10 && patch < 10); 96 return major * 1000 + minor * 100 + patch*10; 97 } 98 99 int toAUVersion() pure const 100 { 101 assert(major < 256 && minor < 256 && patch < 256); 102 return (major << 16) | (minor << 8) | patch; 103 } 104 105 int toAAXPackageVersion() pure const 106 { 107 // For AAX, considered binary-compatible unless major version change 108 return major; 109 } 110 111 void toVST3VersionString(char* outBuffer, int bufLength) const 112 { 113 snprintf(outBuffer, bufLength, "%d.%d.%d", major, minor, patch); 114 115 // DigitalMars's snprintf doesn't always add a terminal zero 116 if (bufLength > 0) 117 outBuffer[bufLength-1] = '\0'; 118 } 119 } 120 121 122 // Statically known features of the plugin. 123 // There is some default for explanation purpose, but you really ought to override them all. 124 // Most of it is redundant with plugin.json, in the future the JSON will be parsed instead. 125 struct PluginInfo 126 { 127 string vendorName = "Witty Audio"; 128 129 /// A four char vendor "unique" ID 130 char[4] vendorUniqueID = "Wity"; 131 132 /// The vendor email adress for support. Can be null. 133 string vendorSupportEmail = null; 134 135 /// Plugin name. 136 string pluginName = "Destructatorizer"; 137 138 /// Plugin web page. Can be null. 139 string pluginHomepage = null; 140 141 /// Used for both VST and AU. 142 /// In AU it is namespaced by the manufacturer. In VST it 143 /// should be unique. While it seems no VST host use this 144 /// ID as a unique way to identify a plugin, common wisdom 145 /// is to try to get a sufficiently random one. 146 char[4] pluginUniqueID = "WiDi"; 147 148 // Plugin version information. 149 // It's important that the version you fill at runtime is identical to the 150 // one in `plugin.json` else you won't pass AU validation. 151 // 152 // Note: For AU, 0.x.y is supposed to mean "do not cache", however it is 153 // unknown what it actually changes. AU caching hasn't caused any problem 154 // and can probably be ignored. 155 PluginVersion publicVersion = PluginVersion(0, 0, 0); 156 157 /// True if the plugin has a graphical UI. Easy way to disable it. 158 bool hasGUI = false; 159 160 /// True if the plugin "is a synth". This has only a semantic effect. 161 bool isSynth = false; 162 163 /// True if the plugin should receive MIDI events. 164 /// Warning: receiving MIDI forces you to call `getNextMidiMessages` 165 /// with the right number of `frames`, every buffer. 166 bool receivesMIDI = false; 167 168 /// True if the plugin sends MIDI events. 169 bool sendsMIDI = false; 170 171 /// Used for being at the right place in list of plug-ins. 172 PluginCategory category; 173 174 /// Used as name of the bundle in VST. 175 string VSTBundleIdentifier; 176 177 /// Used as name of the bundle in AU. 178 string AUBundleIdentifier; 179 180 /// Used as name of the bundle in AAX. 181 string AAXBundleIdentifier; 182 } 183 184 /// This allows to write things life tempo-synced LFO. 185 struct TimeInfo 186 { 187 /// BPM 188 double tempo = 120; 189 190 /// Current time from the beginning of the song in samples. 191 /// This time can easily be negative, since eg. in REAPER 192 /// you can change song beginning with "Project start time" settings. 193 long timeInSamples = 0; 194 195 /// Whether the host sequencer is currently playing 196 bool hostIsPlaying; 197 } 198 199 /// Describe a combination of input channels count and output channels count 200 struct LegalIO 201 { 202 int numInputChannels; 203 int numOutputChannels; 204 } 205 206 /// This is the interface used by the GUI, to reduce coupling and avoid exposing the whole of `Client` to it. 207 /// It should eventually allows to supersede/hide IHostCommand. 208 interface IClient 209 { 210 nothrow: 211 @nogc: 212 /// Requests a resize of the plugin window, notifying the host. 213 /// Returns: `true` if succeeded. 214 /// 215 /// Params: 216 /// width New width of the plugin, in logical pixels. 217 /// height New height of the plugin, in logical pixels. 218 bool requestResize(int widthLogicalPixels, int heightLogicalPixels); 219 220 /// Report the identied host name (DAW). 221 DAW getDAW(); 222 223 /// Gets the plugin format used at runtime. Version identifier may not be enough in the future, in case of 224 /// unity builds. 225 PluginFormat getPluginFormat(); 226 } 227 228 /// Plugin interface, from the client point of view. 229 /// This client has no knowledge of thread-safety, it must be handled externally. 230 /// User plugins derivate from this class. 231 /// Plugin formats wrappers owns one dplug.plugin.Client as a member. 232 /// 233 /// Note: this is an architecture failure since there are 3 users of that interface: 234 /// 1. the plugin "client" implementation (= product), 235 /// 2. the format client 236 /// 3. the UI, directly 237 /// Those should be splitted cleanly. 238 class Client : IClient 239 { 240 public: 241 nothrow: 242 @nogc: 243 244 this() 245 { 246 _info = buildPluginInfo(); 247 248 // Create legal I/O combinations 249 _legalIOs = buildLegalIO(); 250 251 // Create parameters. 252 _params = buildParameters(); 253 254 // Check parameter consistency 255 // This avoid mistake when adding/reordering parameters in a plugin. 256 foreach(size_t i, Parameter param; _params) 257 { 258 // If you fail here, this means your buildParameter() override is incorrect. 259 // Check the values of the index you're giving. 260 // They should be 0, 1, 2, ..., N-1 261 // Maybe you have duplicated a line or misordered them. 262 assert(param.index() == i); 263 264 // Sets owner reference. 265 param.setClientReference(this); 266 } 267 268 // Create presets 269 _presetBank = mallocNew!PresetBank(this, buildPresets()); 270 271 272 _maxFramesInProcess = maxFramesInProcess(); 273 274 _maxInputs = 0; 275 _maxOutputs = 0; 276 foreach(legalIO; _legalIOs) 277 { 278 if (_maxInputs < legalIO.numInputChannels) 279 _maxInputs = legalIO.numInputChannels; 280 if (_maxOutputs < legalIO.numOutputChannels) 281 _maxOutputs = legalIO.numOutputChannels; 282 } 283 284 _inputMidiQueue = makeMidiQueue(); // PERF: only init those for plugins that need it? 285 _outputMidiQueue = makeMidiQueue(); 286 287 if (sendsMIDI) 288 { 289 _midiOutFromUIMutex = makeMutex(); 290 } 291 } 292 293 ~this() 294 { 295 // Destroy graphics 296 if (_graphics !is null) 297 { 298 // Acquire _graphicsIsAvailable forever 299 // so that it's the last time the audio uses it, 300 // and we can wait for its exit in _graphics destructor 301 while(!cas(&_graphicsIsAvailable, true, false)) 302 { 303 // MAYDO: relax CPU 304 } 305 _graphics.destroyFree(); 306 } 307 308 // Destroy presets 309 _presetBank.destroyFree(); 310 311 // Destroy parameters 312 foreach(p; _params) 313 p.destroyFree(); 314 _params.freeSlice(); 315 _legalIOs.freeSlice(); 316 } 317 318 final int maxInputs() pure const nothrow @nogc 319 { 320 return _maxInputs; 321 } 322 323 final int maxOutputs() pure const nothrow @nogc 324 { 325 return _maxOutputs; 326 } 327 328 /// Returns: Array of parameters. 329 final inout(Parameter[]) params() inout nothrow @nogc 330 { 331 return _params; 332 } 333 334 /// Returns: Array of legal I/O combinations. 335 final LegalIO[] legalIOs() nothrow @nogc 336 { 337 return _legalIOs; 338 } 339 340 /// Returns: true if the following I/O combination is a legal one. 341 /// < 0 means "do not check" 342 final bool isLegalIO(int numInputChannels, int numOutputChannels) pure const nothrow @nogc 343 { 344 foreach(io; _legalIOs) 345 if ( ( (numInputChannels < 0) 346 || 347 (io.numInputChannels == numInputChannels) ) 348 && 349 ( (numOutputChannels < 0) 350 || 351 (io.numOutputChannels == numOutputChannels) ) 352 ) 353 return true; 354 355 return false; 356 } 357 358 /// Returns: Array of presets. 359 final PresetBank presetBank() nothrow @nogc 360 { 361 return _presetBank; 362 } 363 364 /// Returns: The parameter indexed by index. 365 final inout(Parameter) param(int index) inout nothrow @nogc 366 { 367 return _params.ptr[index]; 368 } 369 370 /// Returns: true if index is a valid parameter index. 371 final bool isValidParamIndex(int index) const nothrow @nogc 372 { 373 return index >= 0 && index < _params.length; 374 } 375 376 /// Returns: true if index is a valid input index. 377 final bool isValidInputIndex(int index) nothrow @nogc 378 { 379 return index >= 0 && index < maxInputs(); 380 } 381 382 /// Returns: true if index is a valid output index. 383 final bool isValidOutputIndex(int index) nothrow @nogc 384 { 385 return index >= 0 && index < maxOutputs(); 386 } 387 388 /// Note: openGUI, getGUISize, getGraphics and closeGUI are guaranteed 389 /// synchronized by the client implementation 390 /// Only allowed for client implementation. 391 final void* openGUI(void* parentInfo, void* controlInfo, GraphicsBackend backend) nothrow @nogc 392 { 393 createGraphicsLazily(); 394 assert(_hostCommand !is null); 395 return (cast(IGraphics)_graphics).openUI(parentInfo, controlInfo, this, backend); 396 } 397 398 /// Only allowed for client implementation. 399 final bool getGUISize(int* widthLogicalPixels, int* heightLogicalPixels) nothrow @nogc 400 { 401 createGraphicsLazily(); 402 auto graphics = (cast(IGraphics)_graphics); 403 if (graphics) 404 { 405 graphics.getGUISize(widthLogicalPixels, heightLogicalPixels); 406 return true; 407 } 408 else 409 return false; 410 } 411 412 /// Close the plugin UI if one was opened. 413 /// Note: OBS Studio will happily call effEditClose without having called effEditOpen. 414 /// Only allowed for client implementation. 415 final void closeGUI() nothrow @nogc 416 { 417 auto graphics = (cast(IGraphics)_graphics); 418 if (graphics) 419 { 420 graphics.closeUI(); 421 } 422 } 423 424 /// This creates the GUIGraphics object lazily, and return it without synchronization. 425 /// Only allowed for client implementation. 426 final IGraphics getGraphics() 427 { 428 createGraphicsLazily(); 429 return (cast(IGraphics)_graphics); 430 } 431 432 // This should be called only by a client implementation. 433 void setParameterFromHost(int index, float value) nothrow @nogc 434 { 435 param(index).setFromHost(value); 436 } 437 438 /// Override if you create a plugin with UI. 439 /// The returned IGraphics must be allocated with `mallocNew`. 440 /// `plugin.json` needs to have a "hasGUI" key equal to true, else this callback is never called. 441 IGraphics createGraphics() nothrow @nogc 442 { 443 return null; 444 } 445 446 /// Intended from inside the audio thread, in `process`. 447 /// Enqueue one MIDI message on the output MIDI priority queue, so that it is 448 /// eventually sent. 449 /// Its offset is relative to the current buffer, and you can send messages arbitrarily 450 /// in the future too. 451 void sendMIDIMessage(MidiMessage message) nothrow @nogc 452 { 453 _outputMidiQueue.enqueue(message); 454 } 455 456 /// Send MIDI from inside the UI. 457 /// Intended to be called from inside an UI event callback. 458 /// 459 /// Enqueue several MIDI messages in a synchronized manner, so that they are sent all at once, 460 /// as early as possible as "live" MIDI messages. 461 /// No guarantee of any timing for these messages, for example this can be in response to 462 /// a key press on a virtual keyboard. 463 /// The messages don't have to be ordered if they are spaced, but have to be if they 464 /// have the same `offset`. 465 /// 466 /// Note: It is guaranteed that all messages passed this way will keep their offset 467 /// relationship in MIDI output. (Typically such a messages would all have a zero 468 /// timestamp). 469 /// Though they are sent as soon as possible in a best effort manner, their relative 470 /// offset is preserved. 471 /// Its offset is relative to the current buffer, and you can send messages arbitrarily 472 /// in the future too. 473 void sendMIDIMessagesFromUI(const(MidiMessage)[] messages) nothrow @nogc 474 { 475 _midiOutFromUIMutex.lock(); 476 477 foreach(msg; messages) 478 _outputMidiFromUI.pushBack(msg); 479 480 _midiOutFromUIMutex.unlock(); 481 } 482 483 /// Getter for the IGraphics interface 484 /// This is intended ONLY for the audio thread inside processing and has acquire semantics. 485 /// Not reentrant! You can't call this twice without a graphicsRelease first. 486 /// THIS CAN RETURN NULL EVEN AFTER HAVING RETURNED NON-NULL AT ONE POINT. 487 /// Returns: null if feedback from audio thread is not welcome. 488 final IGraphics graphicsAcquire() nothrow @nogc 489 { 490 if (cas(&_graphicsIsAvailable, true, false)) // exclusive, since there is only one audio thread normally 491 return _graphics; 492 else 493 return null; 494 } 495 496 /// Mirror function to release the IGraphics from the audio-thread. 497 /// Do not call if graphicsAcquire() returned `null`. 498 final void graphicsRelease() nothrow @nogc 499 { 500 // graphicsAcquire should have been called before 501 // MAYDO: which memory order here? Don't looks like we need a barrier. 502 atomicStore(_graphicsIsAvailable, true); 503 } 504 505 // Getter for the IHostCommand interface 506 final IHostCommand hostCommand() nothrow @nogc 507 { 508 return _hostCommand; 509 } 510 511 /// Override to clear state (eg: resize and clear delay lines) and allocate buffers. 512 /// Note: `reset` should not be called directly by plug-in format implementations. Use `resetFromHost` if you write a new client. 513 abstract void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc; 514 515 /// Override to set the plugin latency in samples. 516 /// Plugin latency can depend on `sampleRate` but no other value. 517 /// If you want your latency to depend on a `Parameter` your only choice is to 518 /// pessimize the needed latency and compensate in the process callback. 519 /// Returns: Plugin latency in samples. 520 /// Note: this can absolutely be called before `reset` was called, be prepared. 521 int latencySamples(double sampleRate) nothrow @nogc 522 { 523 return 0; // By default, no latency introduced by plugin 524 } 525 526 /// Override to set the plugin tail length in seconds. 527 /// 528 /// This is the amount of time before silence is reached with a silent input, on the worst 529 /// possible settings. 530 /// 531 /// Returns: Plugin tail size in seconds. 532 /// - Returning 0.0f means that as soon as your input is silent, the output will be silent. 533 /// It isn't a special value. 534 /// - Returning `float.infinity` means that the host should not optimize calls to `processAudio`. 535 /// If your plugin is a synth, or an effect generating sound, you MUST return `float.infinity`. 536 /// - Otherwise, returning a particular tail size is the regular meaning. 537 /// 538 float tailSizeInSeconds() nothrow @nogc 539 { 540 // Default: always call `processAudio`. This is safest. 541 // 542 // It is recommended to setup this override at one point in developemnt, especially for an effect plugin. 543 // This allows VST3 and AU hosts to optimize things. 544 // 545 // For an effect 2 secs is a good starting point. Which should be safe for most effects plugins except delay or reverb 546 // Warning: plugins have often MUCH more tail size than expected! 547 // Don't reduce to a shorter time unless you know for sure what you are doing. 548 // Synths, and effects generating audio from MIDI should return `float.infinity`. 549 550 return float.infinity; 551 } 552 553 /// Override to declare the maximum number of samples to accept 554 /// If greater, the audio buffers will be splitted up. 555 /// This splitting have several benefits: 556 /// - help allocating temporary audio buffers on the stack 557 /// - keeps memory usage low and reuse it 558 /// - allow faster-than-buffer-size parameter changes (VST3) 559 /// Returns: Maximum number of samples 560 /// Warning: Some buffersize-related bugs might be hidden by having sub-buffers. 561 /// If yoy are looking for a buffersize bug, maybe try to disable sub-buffers 562 /// by returning the default 0. 563 int maxFramesInProcess() nothrow @nogc 564 { 565 return 0; // default returns 0 which means "do not split buffers" 566 } 567 568 /// Process some audio. 569 /// Override to make some noise. 570 /// In processAudio you are always guaranteed to get valid pointers 571 /// to all the channels the plugin requested. 572 /// Unconnected input pins are zeroed. 573 /// This callback is the only place you may call `getNextMidiMessages()` (it is 574 /// even required for plugins receiving MIDI). 575 /// 576 /// Number of frames are guaranteed to be less or equal to what the last reset() call said. 577 /// Number of inputs and outputs are guaranteed to be exactly what the last reset() call said. 578 /// Warning: Do not modify the output pointers! 579 abstract void processAudio(const(float*)[] inputs, // array of input channels 580 float*[] outputs, // array of output channels 581 int frames, // number of sample in each input & output channel 582 TimeInfo timeInfo // time information associated with this signal frame 583 ) nothrow @nogc; 584 585 /// Should only be called in `processAudio`. 586 /// This return a slice of MIDI messages corresponding to the next `frames` samples. 587 /// Useful if you don't want to process messages every samples, or every split buffer. 588 final const(MidiMessage)[] getNextMidiMessages(int frames) nothrow @nogc 589 { 590 return _inputMidiQueue.getNextMidiMessages(frames); 591 } 592 593 /// Returns a new default preset. 594 final Preset makeDefaultPreset() nothrow @nogc 595 { 596 // MAYDO: use mallocSlice for perf 597 auto values = makeVec!float(); 598 foreach(param; _params) 599 values.pushBack(param.getNormalizedDefault()); 600 601 // Perf: one could avoid malloc to copy those arrays again there 602 float[] valuesSlice = values.releaseData; 603 Preset result = mallocNew!Preset("Default", valuesSlice); 604 free(valuesSlice.ptr); 605 return result; 606 } 607 608 // Getters for fields in _info 609 610 final bool hasGUI() pure const nothrow @nogc 611 { 612 return _info.hasGUI; 613 } 614 615 final bool isSynth() pure const nothrow @nogc 616 { 617 return _info.isSynth; 618 } 619 620 final bool receivesMIDI() pure const nothrow @nogc 621 { 622 return _info.receivesMIDI; 623 } 624 625 final bool sendsMIDI() pure const nothrow @nogc 626 { 627 return _info.sendsMIDI; 628 } 629 630 final string vendorName() pure const nothrow @nogc 631 { 632 return _info.vendorName; 633 } 634 635 final char[4] getVendorUniqueID() pure const nothrow @nogc 636 { 637 return _info.vendorUniqueID; 638 } 639 640 final string getVendorSupportEmail() pure const nothrow @nogc 641 { 642 return _info.vendorSupportEmail; 643 } 644 645 final string pluginName() pure const nothrow @nogc 646 { 647 return _info.pluginName; 648 } 649 650 final string pluginHomepage() pure const nothrow @nogc 651 { 652 return _info.pluginHomepage; 653 } 654 655 final PluginCategory pluginCategory() pure const nothrow @nogc 656 { 657 return _info.category; 658 } 659 660 final string VSTBundleIdentifier() pure const nothrow @nogc 661 { 662 return _info.VSTBundleIdentifier; 663 } 664 665 final string AUBundleIdentifier() pure const nothrow @nogc 666 { 667 return _info.AUBundleIdentifier; 668 } 669 670 final string AAXBundleIdentifier() pure const nothrow @nogc 671 { 672 return _info.AAXBundleIdentifier; 673 } 674 675 /// Returns: Plugin "unique" ID. 676 final char[4] getPluginUniqueID() pure const nothrow @nogc 677 { 678 return _info.pluginUniqueID; 679 } 680 681 /// Returns: Plugin full name "$VENDOR $PRODUCT" 682 final void getPluginFullName(char* p, int bufLength) const nothrow @nogc 683 { 684 snprintf(p, bufLength, "%.*s %.*s", 685 cast(int)(_info.vendorName.length), _info.vendorName.ptr, 686 cast(int)(_info.pluginName.length), _info.pluginName.ptr); 687 688 // DigitalMars's snprintf doesn't always add a terminal zero 689 if (bufLength > 0) 690 { 691 p[bufLength-1] = '\0'; 692 } 693 } 694 695 /// Returns: Plugin version in x.x.x.x decimal form. 696 final PluginVersion getPublicVersion() pure const nothrow @nogc 697 { 698 return _info.publicVersion; 699 } 700 701 /// Boilerplate function to get the value of a `FloatParameter`, for use in `processAudio`. 702 final T readParam(T)(int paramIndex) nothrow @nogc 703 if (is(T == float)) 704 { 705 auto p = param(paramIndex); 706 assert(cast(FloatParameter)p !is null); // check it's a FloatParameter 707 return unsafeObjectCast!FloatParameter(p).valueAtomic(); 708 } 709 710 /// Boilerplate function to get the value of an `IntParameter`, for use in `processAudio`. 711 final T readParam(T)(int paramIndex) nothrow @nogc 712 if (is(T == int) && !is(T == enum)) 713 { 714 auto p = param(paramIndex); 715 assert(cast(IntegerParameter)p !is null); // check it's an IntParameter 716 return unsafeObjectCast!IntegerParameter(p).valueAtomic(); 717 } 718 719 /// Boilerplate function to get the value of an `EnumParameter`, for use in `processAudio`. 720 final T readParam(T)(int paramIndex) nothrow @nogc 721 if (is(T == enum)) 722 { 723 auto p = param(paramIndex); 724 assert(cast(EnumParameter)p !is null); // check it's an EnumParameter 725 return cast(T)(unsafeObjectCast!EnumParameter(p).valueAtomic()); 726 } 727 728 /// Boilerplate function to get the value of a `BoolParameter`,for use in `processAudio`. 729 final T readParam(T)(int paramIndex) nothrow @nogc 730 if (is(T == bool)) 731 { 732 auto p = param(paramIndex); 733 assert(cast(BoolParameter)p !is null); // check it's a BoolParameter 734 return unsafeObjectCast!BoolParameter(p).valueAtomic(); 735 } 736 737 /// For plugin format clients only. 738 final void setHostCommand(IHostCommand hostCommand) nothrow @nogc 739 { 740 _hostCommand = hostCommand; 741 742 // In VST3, for accuracy of parameter automation we choose to split buffers in chunks of maximum 512. 743 // This avoids painful situations where parameters could higher precision. 744 if (hostCommand.getPluginFormat() == PluginFormat.vst3) 745 { 746 if (_maxFramesInProcess == 0 || _maxFramesInProcess > 512) 747 _maxFramesInProcess = 512; 748 } 749 } 750 751 /// For plugin format clients only. 752 /// Enqueues an incoming MIDI message. 753 void enqueueMIDIFromHost(MidiMessage message) 754 { 755 _inputMidiQueue.enqueue(message); 756 } 757 758 /// For plugin format clients only. 759 /// This return a slice of MIDI messages to be sent for this (whole unsplit) buffer. 760 /// Internally, you need to either use split-buffering from this file, or if the format does 761 /// its own buffer split it needs to call `accumulateOutputMIDI` itself. 762 final const(MidiMessage)[] getAccumulatedOutputMidiMessages() nothrow @nogc 763 { 764 return _outputMidiMessages[]; 765 } 766 /// For plugin format clients only. 767 /// Clear MIDI output buffer. Call it before `processAudioFromHost` or `accumulateOutputMIDI`. 768 /// What it also does it get all MIDI message from the UI, and add them to the priority queue, 769 /// so that they may be accumulated like normal MIDI sent from the process callback. 770 final void clearAccumulatedOutputMidiMessages() nothrow @nogc 771 { 772 assert(sendsMIDI()); 773 774 _outputMidiMessages.clearContents(); 775 776 // Enqueue all messages from UI in the priority queue. 777 _midiOutFromUIMutex.lock(); 778 foreach(msg; _outputMidiFromUI[]) 779 _outputMidiQueue.enqueue(msg); 780 _outputMidiFromUI.clearContents(); 781 _midiOutFromUIMutex.unlock(); 782 } 783 784 /// For plugin format clients only. 785 /// Calls processAudio repeatedly, splitting the buffers. 786 /// Splitting allow to decouple memory requirements from the actual host buffer size. 787 /// There is few performance penalty above 512 samples. 788 void processAudioFromHost(float*[] inputs, 789 float*[] outputs, 790 int frames, 791 TimeInfo timeInfo, 792 bool doNotSplit = false, // flag that exist in case the plugin client want to split itself 793 ) nothrow @nogc 794 { 795 // In debug mode, fill all output audio buffers with `float.nan`. 796 // This avoids a plug-in forgetting to fill output buffers, which can happen if you 797 // implement silence detection badly. 798 // CAUTION: this assumes inputs and outputs buffers do not point into the same memory areas 799 debug 800 { 801 for (int k = 0; k < outputs.length; ++k) 802 { 803 float* pOut = outputs[k]; 804 pOut[0..frames] = float.nan; 805 } 806 } 807 808 if (_maxFramesInProcess == 0 || doNotSplit) 809 { 810 processAudio(inputs, outputs, frames, timeInfo); 811 if (sendsMIDI) accumulateOutputMIDI(frames); 812 } 813 else 814 { 815 // Slice audio in smaller parts 816 while (frames > 0) 817 { 818 // Note: the last slice will be smaller than the others 819 int sliceLength = frames; 820 if (sliceLength > _maxFramesInProcess) 821 sliceLength = _maxFramesInProcess; 822 823 processAudio(inputs, outputs, sliceLength, timeInfo); 824 if (sendsMIDI) accumulateOutputMIDI(sliceLength); 825 826 // offset all input buffer pointers 827 for (int i = 0; i < cast(int)inputs.length; ++i) 828 inputs[i] = inputs[i] + sliceLength; 829 830 // offset all output buffer pointers 831 for (int i = 0; i < cast(int)outputs.length; ++i) 832 outputs[i] = outputs[i] + sliceLength; 833 834 frames -= sliceLength; 835 836 // timeInfo must be updated 837 timeInfo.timeInSamples += sliceLength; 838 } 839 assert(frames == 0); 840 } 841 } 842 843 /// For VST3 client only. Format clients that split the buffers themselves (for automation precision) 844 /// Need as well to accumulate MIDI output themselves. 845 /// See_also: `getAccumulatedOutputMidiMessages` for how to get those accumulated messages for the whole buffer. 846 final void accumulateOutputMIDI(int frames) 847 { 848 _outputMidiQueue.accumNextMidiMessages(_outputMidiMessages, frames); 849 } 850 851 /// For plugin format clients only. 852 /// Calls `reset()`. 853 /// Must be called by the audio thread. 854 void resetFromHost(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc 855 { 856 // Clear outstanding MIDI messages (now invalid) 857 _inputMidiQueue.initialize(); // MAYDO: should it push a MIDI message to mute all voices? 858 859 _outputMidiQueue.initialize(); // TODO: that sounds fishy, what if we have send a note on but not the note off? 860 861 // We potentially give to the client implementation a lower value 862 // for the maximum number of frames 863 if (_maxFramesInProcess != 0 && _maxFramesInProcess < maxFrames) 864 maxFrames = _maxFramesInProcess; 865 866 // Calls the reset virtual call 867 reset(sampleRate, maxFrames, numInputs, numOutputs); 868 } 869 870 /// For use by plugin format clients. This gives the buffer split size to use. 871 /// (0 == no split). 872 /// This is useful in the cast the format client wants to split buffers by itself. 873 final int getBufferSplitMaxFrames() 874 { 875 return _maxFramesInProcess; 876 } 877 878 // <IClient> 879 override bool requestResize(int widthLogicalPixels, int heightLogicalPixels) 880 { 881 if (_hostCommand is null) 882 return false; 883 884 return _hostCommand.requestResize(widthLogicalPixels, heightLogicalPixels); 885 } 886 887 override DAW getDAW() 888 { 889 assert(_hostCommand !is null); 890 return _hostCommand.getDAW(); 891 } 892 893 override PluginFormat getPluginFormat() 894 { 895 assert(_hostCommand !is null); 896 return _hostCommand.getPluginFormat(); 897 } 898 899 // </IClient> 900 901 protected: 902 903 /// Override this method to implement parameter creation. 904 /// This is an optional overload, default implementation declare no parameters. 905 /// The returned slice must be allocated with `malloc`/`mallocSlice` and contains 906 /// `Parameter` objects created with `mallocEmplace`. 907 Parameter[] buildParameters() 908 { 909 return []; 910 } 911 912 /// Override this methods to load/fill presets. 913 /// This function must return a slice allocated with `malloc`, 914 /// that contains presets crteated with `mallocEmplace`. 915 Preset[] buildPresets() nothrow @nogc 916 { 917 auto presets = makeVec!Preset(); 918 presets.pushBack( makeDefaultPreset() ); 919 return presets.releaseData(); 920 } 921 922 /// Override this method to tell what plugin you are. 923 /// Mandatory override, fill the fields with care. 924 /// Note: this should not be called by a plugin client implementation directly. 925 /// Access the content of PluginInfo through the various accessors. 926 abstract PluginInfo buildPluginInfo(); 927 928 /// Override this method to tell which I/O are legal. 929 /// The returned slice must be allocated with `malloc`/`mallocSlice`. 930 abstract LegalIO[] buildLegalIO(); 931 932 IGraphics _graphics; 933 934 // Used as a flag that _graphics can be used (by audio thread or for destruction) 935 shared(bool) _graphicsIsAvailable = false; 936 937 // Note: when implementing a new plug-in format, the format wrapper has to call 938 // `setHostCommand` and implement `IHostCommand`. 939 IHostCommand _hostCommand = null; 940 941 PluginInfo _info; 942 943 private: 944 Parameter[] _params; 945 946 PresetBank _presetBank; 947 948 LegalIO[] _legalIOs; 949 950 int _maxInputs, _maxOutputs; // maximum number of input/outputs 951 952 // Cache result of maxFramesInProcess(), maximum frame length 953 int _maxFramesInProcess; 954 955 // Container for awaiting MIDI messages. 956 MidiQueue _inputMidiQueue; 957 958 // Priority queue for sending MIDI messages. 959 MidiQueue _outputMidiQueue; 960 961 // Protects MIDI out from UI. 962 UncheckedMutex _midiOutFromUIMutex; 963 964 // Additional, unsorted messages to be sent, courtesy of the UI. 965 Vec!MidiMessage _outputMidiFromUI; 966 967 // Accumulated output MIDI messages, for one unsplit buffer. 968 // Output MIDI messages, if any, are accumulated there. 969 Vec!MidiMessage _outputMidiMessages; 970 971 final void createGraphicsLazily() 972 { 973 // First GUI opening create the graphics object 974 // no need to protect _graphics here since the audio thread 975 // does not write to it. 976 if ( (_graphics is null) && hasGUI()) 977 { 978 // Why is the IGraphics created lazily? This allows to load a plugin very quickly, 979 // without opening its logical UI 980 IGraphics graphics = createGraphics(); 981 982 // Don't forget to override the createGraphics method! 983 assert(graphics !is null); 984 985 _graphics = graphics; 986 987 // Now that the UI is fully created, we enable the audio thread to use it 988 atomicStore(_graphicsIsAvailable, true); 989 } 990 } 991 } 992 993 /// Should be called in Client class during compile time 994 /// to parse a `PluginInfo` from a supplied json file. 995 PluginInfo parsePluginInfo(string json) 996 { 997 import std.json; 998 import std.string; 999 import std.conv; 1000 1001 JSONValue j = parseJSON(json); 1002 1003 static bool toBoolean(JSONValue value) 1004 { 1005 static if (__VERSION__ >= 2087) 1006 { 1007 if (value.type == JSONType.true_) 1008 return true; 1009 if (value.type == JSONType.false_) 1010 return false; 1011 } 1012 else 1013 { 1014 if (value.type == JSON_TYPE.TRUE) 1015 return true; 1016 if (value.type == JSON_TYPE.FALSE) 1017 return false; 1018 } 1019 throw new Exception(format("Expected a boolean, got %s instead", value)); 1020 } 1021 1022 // Check that a string is "x.y.z" 1023 // FUTURE: support larger integers than 0 to 9 in the string 1024 static PluginVersion parsePluginVersion(string value) 1025 { 1026 bool isDigit(char ch) 1027 { 1028 return ch >= '0' && ch <= '9'; 1029 } 1030 1031 if ( value.length != 5 || 1032 !isDigit(value[0]) || 1033 value[1] != '.' || 1034 !isDigit(value[2]) || 1035 value[3] != '.' || 1036 !isDigit(value[4])) 1037 { 1038 throw new Exception("\"publicVersion\" should follow the form x.y.z (eg: \"1.0.0\")"); 1039 } 1040 1041 PluginVersion ver; 1042 ver.major = value[0] - '0'; 1043 ver.minor = value[2] - '0'; 1044 ver.patch = value[4] - '0'; 1045 return ver; 1046 } 1047 1048 PluginInfo info; 1049 info.vendorName = j["vendorName"].str; 1050 info.vendorUniqueID = j["vendorUniqueID"].str; 1051 info.pluginName = j["pluginName"].str; 1052 info.pluginUniqueID = j["pluginUniqueID"].str; 1053 1054 if ("vendorSupportEmail" in j) 1055 info.vendorSupportEmail= j["vendorSupportEmail"].str; 1056 1057 if ("pluginHomepage" in j) 1058 info.pluginHomepage = j["pluginHomepage"].str; 1059 1060 if ("isSynth" in j) 1061 info.isSynth = toBoolean(j["isSynth"]); 1062 info.hasGUI = toBoolean(j["hasGUI"]); 1063 if ("receivesMIDI" in j) 1064 info.receivesMIDI = toBoolean(j["receivesMIDI"]); 1065 if ("sendsMIDI" in j) 1066 info.sendsMIDI = toBoolean(j["sendsMIDI"]); 1067 1068 // Plugins that sends MIDI must also receives MIDI. 1069 if (info.sendsMIDI && !info.receivesMIDI) 1070 throw new Exception("A plugin that sends MIDI must also receives MIDI. Caution: a plugin that receives MIDI must call getNextMidiMessages() in the audio callback"); 1071 1072 info.publicVersion = parsePluginVersion(j["publicVersion"].str); 1073 1074 string CFBundleIdentifierPrefix = j["CFBundleIdentifierPrefix"].str; 1075 1076 string sanitizedName = sanitizeBundleString(info.pluginName); 1077 info.VSTBundleIdentifier = CFBundleIdentifierPrefix ~ ".vst." ~ sanitizedName; 1078 info.AUBundleIdentifier = CFBundleIdentifierPrefix ~ ".audiounit." ~ sanitizedName; 1079 info.AAXBundleIdentifier = CFBundleIdentifierPrefix ~ ".aax." ~ sanitizedName; 1080 1081 PluginCategory category = parsePluginCategory(j["category"].str); 1082 if (category == PluginCategory.invalid) 1083 throw new Exception("Invalid \"category\" in plugin.json. Check out dplug.client.daw for valid values (eg: \"effectDynamics\")."); 1084 info.category = category; 1085 1086 // See Issue #581. 1087 // Check that we aren't leaking secrets in this build, through `import("plugin.json")`. 1088 void checkNotLeakingPassword(string key) 1089 { 1090 if (key in j) 1091 { 1092 string pwd = j[key].str; 1093 if (pwd == "!PROMPT") 1094 return; 1095 1096 if (pwd.length > 0 && pwd[0] == '$') 1097 return; // using an envvar 1098 1099 throw new Exception( 1100 "\n*************************** WARNING ***************************\n\n" 1101 ~ " This build is using a plain text password in plugin.json\n" 1102 ~ " This will leak through `import(\"plugin.json\")`\n\n" 1103 ~ " Solutions:\n" 1104 ~ " 1. Use environment variables, such as:\n" 1105 ~ " \"iLokPassword\": \"$ILOK_PASSWORD\"\n" 1106 ~ " 2. Use the special value \"!PROMPT\", such as:\n" 1107 ~ " \"keyPassword-windows\": \"!PROMPT\"\n\n" 1108 ~ "***************************************************************\n"); 1109 } 1110 } 1111 checkNotLeakingPassword("keyPassword-windows"); 1112 checkNotLeakingPassword("iLokPassword"); 1113 1114 void checkNotLeakingNoPrompt(string key) 1115 { 1116 if (key in j) 1117 { 1118 string pwd = j[key].str; 1119 if (pwd.length > 0 && pwd[0] == '$') 1120 return; // using an envvar 1121 1122 throw new Exception( 1123 "\n*************************** WARNING ***************************\n\n" 1124 ~ " This build is using a plain text password in plugin.json\n" 1125 ~ " This will leak through `import(\"plugin.json\")`\n\n" 1126 ~ " Solution:\n" 1127 ~ " Use environment variables, such as:\n" 1128 ~ " \"appSpecificPassword-altool\": \"$APP_SPECIFIC_PASSWORD\"\n\n" 1129 ~ "***************************************************************\n"); 1130 } 1131 } 1132 checkNotLeakingNoPrompt("appSpecificPassword-altool"); 1133 checkNotLeakingNoPrompt("appSpecificPassword-stapler"); 1134 1135 return info; 1136 } 1137 1138 private string sanitizeBundleString(string s) pure 1139 { 1140 string r = ""; 1141 foreach(dchar ch; s) 1142 { 1143 if (ch >= 'A' && ch <= 'Z') 1144 r ~= ch; 1145 else if (ch >= 'a' && ch <= 'z') 1146 r ~= ch; 1147 else if (ch == '.') 1148 r ~= ch; 1149 else 1150 r ~= "-"; 1151 } 1152 return r; 1153 }