1 //----------------------------------------------------------------------------- 2 // LICENSE 3 // (c) 2005, Steinberg Media Technologies GmbH, All Rights Reserved 4 // (c) 2018-2021, Guillaume Piolat (contact@auburnsounds.com) 5 //----------------------------------------------------------------------------- 6 // 7 // This Software Development Kit is licensed under the terms of the General 8 // Public License (GPL) Version 3. 9 // 10 // This source is part of the "Auburn Sounds (Guillaume Piolat) extension to the 11 // Steinberg VST 3 Plug-in SDK". 12 // 13 // Details of that license can be found at: www.gnu.org/licenses/gpl-3.0.html 14 // 15 // Dual-licence: 16 // 17 // The "Auburn Sounds (Guillaume Piolat) extension to the Steinberg VST 3 Plug-in 18 // SDK", hereby referred to as DPLUG:VST3, is a language translation of the VST3 19 // SDK suitable for usage in Dplug. Any Licensee of a currently valid Steinberg 20 // VST 3 Plug-In SDK Licensing Agreement (version 2.2.4 or ulterior, hereby referred 21 // to as the AGREEMENT), is granted by Auburn Sounds (Guillaume Piolat) a non-exclusive, 22 // worldwide, nontransferable license during the term the AGREEMENT to use parts 23 // of DPLUG:VST3 not covered by the AGREEMENT, as if they were originally 24 // inside the Licensed Software Developer Kit mentionned in the AGREEMENT. 25 // Under this licence all conditions that apply to the Licensed Software Developer 26 // Kit also apply to DPLUG:VST3. 27 // 28 //----------------------------------------------------------------------------- 29 module dplug.vst3.client; 30 31 version(VST3): 32 33 import core.atomic; 34 import core.stdc.stdlib: free; 35 import core.stdc.string: strcmp; 36 import core.stdc.stdio: snprintf; 37 38 import dplug.client.client; 39 import dplug.client.params; 40 import dplug.client.graphics; 41 import dplug.client.daw; 42 import dplug.client.midi; 43 44 import dplug.core.nogc; 45 import dplug.core.string; 46 import dplug.core.sync; 47 import dplug.core.vec; 48 import dplug.core.runtime; 49 import dplug.vst3.ftypes; 50 import dplug.vst3.ivstaudioprocessor; 51 import dplug.vst3.ivsteditcontroller; 52 import dplug.vst3.iplugview; 53 import dplug.vst3.ivstcomponent; 54 import dplug.vst3.ipluginbase; 55 import dplug.vst3.ibstream; 56 import dplug.vst3.ivstunit; 57 58 //debug = logVST3Client; 59 60 61 // Note: the VST3 client assumes shared memory 62 class VST3Client : IAudioProcessor, IComponent, IEditController, IEditController2, IUnitInfo, IMidiMapping 63 { 64 public: 65 nothrow: 66 @nogc: 67 68 this(Client client) 69 { 70 debug(logVST3Client) debugLog(">VST3Client.this()"); 71 debug(logVST3Client) scope(exit) debugLog("<VST3Client.this()"); 72 _client = client; 73 _hostCommand = mallocNew!VST3HostCommand(this); 74 _client.setHostCommand(_hostCommand); 75 76 // Store number of parameters. 77 _numParams = cast(int) _client.params().length; 78 79 // if no preset, pretend to be a continuous parameter 80 _presetStepCount = _client.presetBank.numPresets() - 1; 81 if (_presetStepCount < 0) _presetStepCount = 0; 82 83 _maxInputs = client.maxInputs(); 84 _inputScratchBuffers = mallocSlice!(Vec!float)(_maxInputs); 85 for (int i = 0; i < _maxInputs; ++i) 86 _inputScratchBuffers[i] = makeVec!float(); 87 88 version(futureVST3MIDICC) 89 initializeMIDICCValues(); 90 } 91 92 ~this() 93 { 94 debug(logVST3Client) debugLog(">VST3Client.~this()"); 95 debug(logVST3Client) scope(exit) debugLog("<VST3Client.~this()"); 96 destroyFree(_client); 97 _client = null; 98 99 for (int i = 0; i < _maxInputs; ++i) 100 _inputScratchBuffers[i].destroy(); 101 _inputScratchBuffers.freeSlice(); 102 103 _zeroesBuffer.reallocBuffer(0); 104 _outputScratchBuffer.reallocBuffer(0); 105 106 destroyFree(_hostCommand); 107 _hostCommand = null; 108 109 _inputPointers.reallocBuffer(0); 110 _outputPointers.reallocBuffer(0); 111 } 112 113 // Implements all COM interfaces needed 114 mixin QUERY_INTERFACE_SPECIAL_CASE_IUNKNOWN!(IAudioProcessor, 115 IComponent, 116 IEditController, 117 IEditController2, 118 IPluginBase, 119 IUnitInfo, 120 IMidiMapping); 121 122 mixin IMPLEMENT_REFCOUNT; 123 124 125 // Implements IPluginBase 126 127 /** The host passes a number of interfaces as context to initialize the Plug-in class. 128 @note Extensive memory allocations etc. should be performed in this method rather than in the class' constructor! 129 If the method does NOT return kResultOk, the object is released immediately. In this case terminate is not called! */ 130 extern(Windows) override tresult initialize(FUnknown context) 131 { 132 debug(logVST3Client) debugLog(">initialize()".ptr); 133 debug(logVST3Client) scope(exit) debugLog("<initialize()".ptr); 134 135 setHostApplication(context); 136 137 // Create buses 138 int maxInputs = _client.maxInputs(); 139 int maxOutputs = _client.maxOutputs(); 140 bool receivesMIDI = _client.receivesMIDI(); 141 bool sendsMIDI = _client.sendsMIDI(); 142 143 _audioInputs = makeVec!Bus; 144 _audioOutputs = makeVec!Bus; 145 _eventInputs = makeVec!Bus; 146 147 _sampleRate = -42.0f; // so that a latency change is sent at next `setupProcessing` 148 149 if (maxInputs) 150 { 151 Bus busAudioIn; 152 busAudioIn.active = true; 153 busAudioIn.speakerArrangement = getSpeakerArrangement(maxInputs); 154 with(busAudioIn.info) 155 { 156 mediaType = kAudio; 157 direction = kInput; 158 channelCount = maxInputs; 159 setName("Audio Input"w); 160 busType = kMain; 161 flags = BusInfo.BusFlags.kDefaultActive; 162 } 163 _audioInputs.pushBack(busAudioIn); 164 } 165 166 if (maxOutputs) 167 { 168 Bus busAudioOut; 169 busAudioOut.active = true; 170 busAudioOut.speakerArrangement = getSpeakerArrangement(maxOutputs); 171 with(busAudioOut.info) 172 { 173 mediaType = kAudio; 174 direction = kOutput; 175 channelCount = maxOutputs; 176 setName("Audio Output"w); 177 busType = kMain; 178 flags = BusInfo.BusFlags.kDefaultActive; 179 } 180 _audioOutputs.pushBack(busAudioOut); 181 } 182 183 if (receivesMIDI) 184 { 185 Bus busEventsIn; 186 busEventsIn.active = true; 187 busEventsIn.speakerArrangement = 0; // whatever 188 with(busEventsIn.info) 189 { 190 mediaType = kEvent; 191 direction = kInput; 192 193 // Impact of that change unknown! Is it safe to pass from channelCount = 1 to 16? 194 // Should we even expose 16 MIDI channels for instruments that are not multi-part? 195 // FUTURE: more bus control, providing MIDI channel count in plugin.json 196 version(futureVST3MIDICC) 197 { 198 channelCount = NUM_MIDI_CHANNELS_INPUT; 199 } 200 else 201 { 202 channelCount = 1; 203 } 204 setName("MIDI Input"w); 205 busType = kMain; 206 flags = BusInfo.BusFlags.kDefaultActive; 207 } 208 _eventInputs.pushBack(busEventsIn); 209 } 210 211 if (sendsMIDI) 212 { 213 Bus busEventsOut; 214 busEventsOut.active = true; 215 busEventsOut.speakerArrangement = 0; // whatever 216 with(busEventsOut.info) 217 { 218 mediaType = kEvent; 219 direction = kOutput; 220 channelCount = 1; 221 setName("MIDI Output"w); 222 busType = kMain; 223 flags = BusInfo.BusFlags.kDefaultActive; 224 } 225 _eventOutputs.pushBack(busEventsOut); 226 } 227 228 return kResultOk; 229 } 230 231 /** This function is called before the Plug-in is unloaded and can be used for 232 cleanups. You have to release all references to any host application interfaces. */ 233 extern(Windows) override tresult terminate() 234 { 235 debug(logVST3Client) debugLog("terminate()".ptr); 236 debug(logVST3Client) scope(exit) debugLog("terminate()".ptr); 237 if (_hostApplication !is null) 238 { 239 _hostApplication.release(); 240 _hostApplication = null; 241 } 242 return kResultOk; 243 } 244 245 // Implements IComponent 246 247 extern(Windows) override tresult getControllerClassId (TUID* classId) 248 { 249 // No need to implement since we "did not succeed to separate component from controller" 250 return kNotImplemented; 251 } 252 253 extern(Windows) override tresult setIoMode (IoMode mode) 254 { 255 // Unused in every VST3 SDK example 256 return kNotImplemented; 257 } 258 259 extern(Windows) override int32 getBusCount (MediaType type, BusDirection dir) 260 { 261 Vec!Bus* busList = getBusList(type, dir); 262 if (busList is null) 263 return 0; 264 return cast(int)( busList.length ); 265 } 266 267 extern(Windows) override tresult getBusInfo (MediaType type, BusDirection dir, int32 index, ref BusInfo bus /*out*/) 268 { 269 Vec!Bus* busList = getBusList(type, dir); 270 if (busList is null) 271 return kInvalidArgument; 272 if (index >= busList.length) 273 return kResultFalse; 274 bus = (*busList)[index].info; 275 return kResultTrue; 276 } 277 278 extern(Windows) override tresult getRoutingInfo (ref RoutingInfo inInfo, ref RoutingInfo outInfo /*out*/) 279 { 280 // Apparently not needed in any SDK examples 281 return kNotImplemented; 282 } 283 284 extern(Windows) override tresult activateBus (MediaType type, BusDirection dir, int32 index, TBool state) 285 { 286 debug(logVST3Client) debugLog(">activateBus".ptr); 287 debug(logVST3Client) scope(exit) debugLog("<activateBus".ptr); 288 Vec!Bus* busList = getBusList(type, dir); 289 if (busList is null) 290 return kInvalidArgument; 291 if (index >= busList.length) 292 return kResultFalse; 293 Bus* buses = (*busList).ptr; 294 buses[index].active = (state != 0); 295 return kResultTrue; 296 } 297 298 extern(Windows) override tresult setActive (TBool state) 299 { 300 // In some VST3 examples, this place is used to initialize buffers. 301 return kResultOk; 302 } 303 304 extern(Windows) override tresult setStateController (IBStream state) 305 { 306 debug(logVST3Client) debugLog(">setStateController".ptr); 307 debug(logVST3Client) scope(exit) debugLog("<setStateController".ptr); 308 return kNotImplemented; 309 } 310 311 extern(Windows) override tresult getStateController (IBStream state) 312 { 313 debug(logVST3Client) debugLog(">getStateController".ptr); 314 debug(logVST3Client) scope(exit) debugLog("<getStateController".ptr); 315 return kNotImplemented; 316 } 317 318 // Implements IAudioProcessor 319 320 extern(Windows) override tresult setBusArrangements (SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts) 321 { 322 debug(logVST3Client) debugLog(">setBusArrangements".ptr); 323 debug(logVST3Client) scope(exit) debugLog("<setBusArrangements".ptr); 324 325 if (numIns < 0 || numOuts < 0) 326 return kInvalidArgument; 327 int busIn = cast(int) (_audioInputs.length); // 0 or 1 328 int busOut = cast(int) (_audioOutputs.length); // 0 or 1 329 if (numIns > busIn || numOuts > busOut) 330 return kResultFalse; 331 assert(numIns == 0 || numIns == 1); 332 assert(numOuts == 0 || numOuts == 1); 333 334 int reqInputs = 0; 335 int reqOutputs = 0; 336 337 if (numIns == 1) 338 reqInputs = getChannelCount(inputs[0]); 339 if (numOuts == 1) 340 reqOutputs = getChannelCount(outputs[0]); 341 342 if (!_client.isLegalIO(reqInputs, reqOutputs)) 343 return kResultFalse; 344 345 if (numIns == 1) 346 { 347 Bus* pbus = _audioInputs.ptr; 348 pbus[0].speakerArrangement = inputs[0]; 349 pbus[0].info.channelCount = reqInputs; 350 } 351 if (numOuts == 1) 352 { 353 Bus* pbus = _audioOutputs.ptr; 354 pbus[0].speakerArrangement = outputs[0]; 355 pbus[0].info.channelCount = reqOutputs; 356 } 357 return kResultTrue; 358 } 359 360 extern(Windows) override tresult getBusArrangement (BusDirection dir, int32 index, ref SpeakerArrangement arr) 361 { 362 debug(logVST3Client) debugLog(">getBusArrangement".ptr); 363 debug(logVST3Client) scope(exit) debugLog("<getBusArrangement".ptr); 364 365 Vec!Bus* busList = getBusList(kAudio, dir); 366 if (busList is null || index >= cast(int)(busList.length)) 367 return kInvalidArgument; 368 arr = (*busList)[index].speakerArrangement; 369 return kResultTrue; 370 } 371 372 extern(Windows) override tresult canProcessSampleSize (int32 symbolicSampleSize) 373 { 374 return symbolicSampleSize == kSample32 ? kResultTrue : kResultFalse; 375 } 376 377 extern(Windows) override uint32 getLatencySamples () 378 { 379 ScopedForeignCallback!(false, true) scopedCallback; 380 scopedCallback.enter(); 381 return _client.latencySamples(_sampleRateHostPOV); 382 } 383 384 extern(Windows) override tresult setupProcessing (ref ProcessSetup setup) 385 { 386 debug(logVST3Client) debugLog(">setupProcessing".ptr); 387 debug(logVST3Client) scope(exit) debugLog("<setupProcessing".ptr); 388 389 ScopedForeignCallback!(false, true) scopedCallback; 390 scopedCallback.enter(); 391 392 // Find out if this is a new latency, and inform host of latency change if yes. 393 // That is an implicit assumption in Dplug that latency is dependent upon sample-rate. 394 bool sampleRateChanged = (_sampleRate != setup.sampleRate); 395 _sampleRate = setup.sampleRate; 396 atomicStore(_sampleRateHostPOV, cast(float)(setup.sampleRate)); 397 if (sampleRateChanged && _handler) 398 _handler.restartComponent(kLatencyChanged); 399 400 // Pass these new values to the audio thread 401 atomicStore(_sampleRateAudioThreadPOV, cast(float)(setup.sampleRate)); 402 atomicStore(_maxSamplesPerBlockHostPOV, setup.maxSamplesPerBlock); 403 if (setup.symbolicSampleSize != kSample32) 404 return kResultFalse; 405 return kResultOk; 406 } 407 408 extern(Windows) override tresult setProcessing (TBool state) 409 { 410 debug(logVST3Client) debugLog(">setProcessing".ptr); 411 debug(logVST3Client) scope(exit) debugLog("<setProcessing".ptr); 412 if (state) 413 { 414 atomicStore(_shouldInitialize, true); 415 } 416 return kResultOk; 417 } 418 419 extern(Windows) override tresult process (ref ProcessData data) 420 { 421 ScopedForeignCallback!(false, true) scopedCallback; 422 scopedCallback.enter(); 423 424 assert(data.symbolicSampleSize == kSample32); // no conversion to 64-bit supported 425 426 // Call initialize if needed 427 float newSampleRate = atomicLoad!(MemoryOrder.raw)(_sampleRateAudioThreadPOV); 428 int newMaxSamplesPerBlock = atomicLoad!(MemoryOrder.raw)(_maxSamplesPerBlockHostPOV); 429 // find current number of inputs audio channels 430 int numInputs = 0; 431 if (data.numInputs != 0) // 0 or 1 output audio bus in a Dplug plugin 432 numInputs = data.inputs[0].numChannels; 433 434 // find current number of inputs audio channels 435 int numOutputs = 0; 436 if (data.numOutputs != 0) // 0 or 1 output audio bus in a Dplug plugin 437 numOutputs = data.outputs[0].numChannels; 438 439 bool shouldReinit = cas(&_shouldInitialize, true, false); 440 bool sampleRateChanged = (newSampleRate != _sampleRateDSPPOV); 441 bool maxSamplesChanged = (newMaxSamplesPerBlock != _maxSamplesPerBlockDSPPOV); 442 bool inputChanged = (_numInputChannels != numInputs); 443 bool outputChanged = (_numOutputChannels != numOutputs); 444 445 if (shouldReinit || sampleRateChanged || maxSamplesChanged || inputChanged || outputChanged) 446 { 447 _sampleRateDSPPOV = newSampleRate; 448 _maxSamplesPerBlockDSPPOV = newMaxSamplesPerBlock; 449 _numInputChannels = numInputs; 450 _numOutputChannels = numOutputs; 451 _client.resetFromHost(_sampleRateDSPPOV, _maxSamplesPerBlockDSPPOV, _numInputChannels, _numOutputChannels); 452 resizeScratchBuffers(_maxSamplesPerBlockDSPPOV); 453 454 _inputPointers.reallocBuffer(_numInputChannels); 455 _outputPointers.reallocBuffer(_numOutputChannels); 456 } 457 458 // Gather all I/O pointers 459 foreach(chan; 0.._numInputChannels) 460 { 461 float* pInput = data.inputs[0].channelBuffers32[chan]; 462 463 // May be null in case of deactivated bus, in which case we feed zero instead 464 if (pInput is null) 465 pInput = _zeroesBuffer.ptr; 466 _inputPointers[chan] = pInput; 467 } 468 469 foreach(chan; 0.._numOutputChannels) 470 { 471 float* pOutput = data.outputs[0].channelBuffers32[chan]; 472 473 // May be null in case of deactivated bus, in which case we use a garbage buffer instead 474 if (pOutput is null) 475 pOutput = _outputScratchBuffer.ptr; 476 477 _outputPointers[chan] = pOutput; 478 } 479 480 // Deal with input MIDI events (only note on, note off, CC and pitch bend supported so far) 481 // We can pull all MIDI events at once since there is a priority queue to store them. 482 if (data.inputEvents !is null && _client.receivesMIDI()) 483 { 484 IEventList eventList = data.inputEvents; 485 int numEvents = eventList.getEventCount(); 486 foreach(index; 0..numEvents) 487 { 488 Event e; 489 if (eventList.getEvent(index, e) == kResultOk) 490 { 491 int offset = e.sampleOffset; 492 switch(e.type) 493 { 494 case Event.EventTypes.kNoteOnEvent: 495 { 496 ubyte velocity = cast(ubyte)(0.5f + 127.0f * e.noteOn.velocity); 497 ubyte noteNumber = cast(ubyte)(e.noteOn.pitch); 498 _client.enqueueMIDIFromHost( makeMidiMessageNoteOn(offset, e.noteOn.channel, noteNumber, velocity)); 499 break; 500 } 501 502 case Event.EventTypes.kNoteOffEvent: 503 { 504 ubyte noteNumber = cast(ubyte)(e.noteOff.pitch); 505 _client.enqueueMIDIFromHost( makeMidiMessageNoteOff(offset, e.noteOff.channel, noteNumber)); 506 break; 507 } 508 509 case Event.EventTypes.kLegacyMIDICCOutEvent: 510 { 511 if (e.midiCCOut.controlNumber <= 127) 512 { 513 // Note sure why it's there, I'm not sure if any host uses that, 514 // rather than fake CC Parameters. 515 _client.enqueueMIDIFromHost( 516 makeMidiMessage(offset, e.midiCCOut.channel, MidiStatus.controlChange, e.midiCCOut.controlNumber, e.midiCCOut.value)); 517 } 518 else if (e.midiCCOut.controlNumber == 129) 519 { 520 _client.enqueueMIDIFromHost( 521 makeMidiMessage(offset, e.midiCCOut.channel, MidiStatus.pitchBend, e.midiCCOut.value, e.midiCCOut.value2)); 522 } 523 // TODO: why not control 128?? 524 break; 525 } 526 527 default: 528 // unsupported events 529 } 530 } 531 } 532 } 533 534 535 int totalFrames = data.numSamples; 536 int bufferSplitMaxFrames = _client.getBufferSplitMaxFrames(); 537 538 // This is a Dplug curiosity: in VST3 buffers are always split to a max of 512 samples. 539 // See: https://github.com/AuburnSounds/Dplug/issues/368 540 // Hence, this assertion holds true. 541 assert(bufferSplitMaxFrames > 0); 542 543 // How many split buffers do we need this buffer? 544 int numSubBuffers = (totalFrames + (bufferSplitMaxFrames - 1)) / bufferSplitMaxFrames; 545 546 updateTimeInfo(data.processContext); 547 548 // Hosts like Cubase gives input and output buffer which are identical. 549 // This creates problems since Dplug assumes the contrary. 550 // Copy the input to scratch buffers to avoid overwrite. 551 for (int chan = 0; chan < numInputs; ++chan) 552 { 553 float* pCopy = _inputScratchBuffers[chan].ptr; 554 float* pInput = _inputPointers[chan]; 555 pCopy[0..totalFrames] = pInput[0..totalFrames]; 556 _inputPointers[chan] = pCopy; 557 } 558 559 // Read parameter changes, sets them. 560 // For changed parameters, fills an array of successive values for each split period + 1. 561 // 562 // Store changes of a single parameter over the current buffer. 563 564 IParameterChanges paramChanges = data.inputParameterChanges; 565 _tracks.clearContents(); 566 if (paramChanges !is null) 567 { 568 // How many parameter tracks do we need this buffer? 569 int numParamChanges = paramChanges.getParameterCount(); 570 571 // For each varying parameter, store values for each subbuffer + 1 for the end value. 572 int numParamValues = numSubBuffers + 1; 573 574 _sharedValues.resize(numParamValues * numParamChanges); 575 576 foreach(index; 0..numParamChanges) 577 { 578 IParamValueQueue queue = paramChanges.getParameterData(index); 579 ParamID id = queue.getParameterId(); 580 int pointCount = queue.getPointCount(); 581 582 // Helper to get quickly a MIDI cc with its channel, from a ParamID 583 int cc; 584 int midiChan; 585 if (isMIDIInputCCParameter(id, midiChan, cc)) 586 { 587 for (int pt = 0; pt < pointCount; ++pt) 588 { 589 int pointTime; 590 ParamValue pointValue; 591 if (kResultTrue == queue.getPoint(pt, pointTime, pointValue)) 592 { 593 assert(pointTime >= 0); // if this fail, we were wrong to trust the hosts 594 595 if (cc == 128) // channel pressure (named just "aftertouch" in VST3) 596 { 597 // Do not mistake for "Poly Aftertouch". 598 // With Channel Pressure, one message is sent out for the entire keyboard. 599 MidiMessage msg = makeMidiMessageChannelPressure(pointTime, midiChan, pointValue); 600 _client.enqueueMIDIFromHost(msg); 601 } 602 else if (cc == 129) // pitch wheel 603 { 604 int ivalue = cast(int)(pointValue * 16384.0f); 605 if (ivalue < 0) 606 ivalue = 0; 607 if (ivalue > 16383) 608 ivalue = 16383; 609 MidiMessage msg = makeMidiMessage(pointTime, midiChan, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7); 610 _client.enqueueMIDIFromHost(msg); 611 } 612 else 613 { 614 // CC 0 to 127 615 MidiMessage msg = makeMidiMessageControlChange(pointTime, midiChan, cast(MidiControlChange)cc, pointValue); 616 _client.enqueueMIDIFromHost(msg); 617 } 618 } 619 } 620 continue; // Jump to next parameter, no need for a parameter track. 621 } 622 623 // If not dealt with, enqueue a parameter track for further processing 624 ParameterTracks track; 625 track.id = id; 626 track.values = (_sharedValues.ptr + (index * numParamValues)); 627 628 double x1 = -1; // position of last point related to current buffer 629 double y1 = getParamNormalized(id); // last known parameter value 630 631 int time = 0; 632 int subbuf = 0; 633 for (int pt = 0; pt < pointCount; ++pt) 634 { 635 int pointTime; 636 ParamValue pointValue; 637 if (kResultTrue == queue.getPoint(pt, pointTime, pointValue)) 638 { 639 } 640 else 641 continue; 642 643 double x2 = pointTime; 644 double y2 = pointValue; 645 double slope = 0; 646 double offset = y1; 647 if (x2 != x1) 648 { 649 slope = (y2 - y1) / (x2 - x1); 650 offset = y1 - (slope * x1); 651 } 652 653 // Fill values for subbuffers in this linear ramp 654 while (time < x2) 655 { 656 assert(time >= x1); 657 double curveValue = (slope * time) + offset; // bufferTime is any position in buffer 658 track.values[subbuf++] = curveValue; 659 time += bufferSplitMaxFrames; 660 } 661 x1 = x2; 662 y1 = y2; 663 } 664 665 // All remaining points are set to be y1 (the last known value) 666 while (subbuf < numParamValues) 667 { 668 track.values[subbuf++] = y1; 669 } 670 671 _tracks.pushBack(track); 672 } 673 } 674 675 if (_client.sendsMIDI) 676 _client.clearAccumulatedOutputMidiMessages(); 677 678 int processedFrames = 0; 679 int subbuf = 0; 680 while(processedFrames < totalFrames) 681 { 682 int blockFrames = totalFrames - processedFrames; 683 if (blockFrames > bufferSplitMaxFrames) 684 blockFrames = bufferSplitMaxFrames; 685 686 for (int track = 0; track < _tracks.length; ++track) 687 { 688 setParamValue(_tracks[track].id, _tracks[track].values[subbuf]); 689 } 690 691 // Support bypass 692 bool bypassed = atomicLoad!(MemoryOrder.raw)(_bypassed); 693 if (bypassed) 694 { 695 int minIO = numInputs; 696 if (minIO > numOutputs) minIO = numOutputs; 697 698 for (int chan = 0; chan < minIO; ++chan) 699 { 700 float* pOut = _outputPointers[chan]; 701 float* pIn = _inputPointers[chan]; 702 for(int i = 0; i < blockFrames; ++i) 703 { 704 pOut[i] = pIn[i]; 705 } 706 } 707 708 for (int chan = minIO; chan < numOutputs; ++chan) 709 { 710 float* pOut = _outputPointers[chan]; 711 for(int i = 0; i < blockFrames; ++i) 712 { 713 pOut[i] = 0.0f; 714 } 715 } 716 } 717 else 718 { 719 // Regular processing 720 bool doNotSplit = true; 721 _client.processAudioFromHost(_inputPointers[0..numInputs], 722 _outputPointers[0..numOutputs], 723 blockFrames, 724 _timeInfo, 725 doNotSplit); 726 727 // Accumulate MIDI output for this sub-buffer, if any 728 if (_client.sendsMIDI()) 729 _client.accumulateOutputMIDI(blockFrames); 730 } 731 732 // advance split buffers 733 for (int chan = 0; chan < numInputs; ++chan) 734 { 735 _inputPointers[chan] = _inputPointers[chan] + blockFrames; 736 } 737 for (int chan = 0; chan < numOutputs; ++chan) 738 { 739 _outputPointers[chan] = _outputPointers[chan] + blockFrames; 740 } 741 742 // In case the next process block has no ProcessContext 743 _timeInfo.timeInSamples += blockFrames; 744 745 processedFrames += blockFrames; 746 subbuf = subbuf + 1; 747 } 748 749 // Send last values for varying parameters 750 for (int track = 0; track < _tracks.length; ++track) 751 { 752 setParamValue(_tracks[track].id, _tracks[track].values[subbuf]); 753 } 754 755 assert(processedFrames == totalFrames); 756 757 // Send MIDI message in bulk 758 if (_client.sendsMIDI) 759 { 760 const(MidiMessage)[] toSend = _client.getAccumulatedOutputMidiMessages(); 761 IEventList outEvents = data.outputEvents; 762 763 if (outEvents !is null && toSend.length != 0) 764 { 765 foreach(ref const(MidiMessage) msg; toSend) 766 { 767 Event e; 768 e.busIndex = 0; 769 e.sampleOffset = msg.offset; 770 e.ppqPosition = 0; 771 e.flags = 0; 772 773 // TODO support the following events: 774 // - MIDI poly after touch 775 // - MIDI channel after touch 776 // - MIDI program change 777 778 if (msg.isNoteOn()) 779 { 780 e.type = Event.EventTypes.kNoteOnEvent; 781 e.noteOn.channel = cast(short) msg.channel(); 782 e.noteOn.pitch = cast(short) msg.noteNumber(); 783 e.noteOn.velocity = msg.noteVelocity() / 127.0f; 784 e.noteOn.length = 0; 785 e.noteOn.tuning = 0; 786 e.noteOn.noteId = -1; 787 outEvents.addEvent(e); 788 } 789 else if (msg.isNoteOff()) 790 { 791 e.type = Event.EventTypes.kNoteOffEvent; 792 e.noteOff.channel = cast(short) msg.channel(); 793 e.noteOff.pitch = cast(short) msg.noteNumber(); 794 e.noteOff.velocity = msg.noteVelocity() / 127.0f; 795 e.noteOff.tuning = 0; 796 e.noteOff.noteId = -1; 797 outEvents.addEvent(e); 798 } 799 else if (msg.isPitchBend()) 800 { 801 e.type = Event.EventTypes.kLegacyMIDICCOutEvent; 802 e.midiCCOut.channel = cast(byte) msg.channel(); 803 e.midiCCOut.controlNumber = 129 /* kPitchBend */; 804 e.midiCCOut.value = msg.data1(); 805 e.midiCCOut.value2 = msg.data2(); 806 outEvents.addEvent(e); 807 } 808 else if (msg.isControlChange()) 809 { 810 e.type = Event.EventTypes.kLegacyMIDICCOutEvent; 811 e.midiCCOut.channel = cast(byte) msg.channel(); 812 e.midiCCOut.controlNumber = cast(ubyte) msg.controlChangeControl(); 813 e.midiCCOut.value = cast(byte) msg.controlChangeValue(); 814 e.midiCCOut.value2 = 0; // TODO: special handling for channe pressure, poly pressure 815 } 816 } 817 } 818 } 819 return kResultOk; 820 } 821 822 void setParamValue(ParamID id, ParamValue value) 823 { 824 version(futureVST3MIDICC) 825 { 826 int midiChan; 827 int cc; 828 if (isMIDIInputCCParameter(id, midiChan, cc)) 829 { 830 midiCCValue(midiChan, cc) = value; 831 } 832 } 833 834 if (id == PARAM_ID_BYPASS) 835 { 836 atomicStore(_bypassed, (value >= 0.5f)); 837 } 838 else if (id == PARAM_ID_PROGRAM_CHANGE) 839 { 840 int presetIndex; 841 if (convertPresetParamToPlain(value, &presetIndex)) 842 { 843 _client.presetBank.loadPresetFromHost(presetIndex); 844 } 845 } 846 else 847 { 848 // Note: VSTHost sends _parameters indices_ instead of ParamID, which is completely wrong. 849 int paramIndex; 850 if (_hostMaySendBadParameterIDs) 851 { 852 paramIndex = convertParamIDToClientParamIndex(convertVST3ParamIndexToParamID(id)); 853 if (!_client.isValidParamIndex(paramIndex)) 854 return; 855 } 856 else 857 paramIndex = convertParamIDToClientParamIndex(id); 858 859 _client.setParameterFromHost(paramIndex, value); 860 } 861 } 862 863 void updateTimeInfo(ProcessContext* context) 864 { 865 if (context !is null) 866 { 867 if (context.state & ProcessContext.kTempoValid) 868 _timeInfo.tempo = context.tempo; 869 _timeInfo.timeInSamples = context.projectTimeSamples; 870 _timeInfo.hostIsPlaying = (context.state & ProcessContext.kPlaying) != 0; 871 } 872 } 873 874 extern(Windows) override uint32 getTailSamples() 875 { 876 float tailSeconds = _client.tailSizeInSeconds(); 877 assert(tailSeconds >= 0); 878 879 double tailInSamples = tailSeconds * cast(double) atomicLoad(_sampleRateHostPOV); 880 881 // Large or infinity? Return infinite tail, which should disable "smart disable". 882 if (tailInSamples + 0.5 >= kInfiniteTail) 883 return kInfiniteTail; 884 885 long samples = cast(long)(tailInSamples + 0.5); 886 assert(samples >= 0 && samples <= kInfiniteTail); 887 888 if (samples < 2) 889 { 890 // In case some hosts do not understand that zero there doesn't mean the same thing that in VST2 891 // (where it meant infinity tail), we avoid zero. 892 // Because in VST2 1 was a special value that means "no tail", we also avoid 1. 893 // So, we return 2 so that even a buggy host would accept our value. 894 // The number of plugin that actually have a zero tail size is really super small. 895 return 2; 896 } 897 898 return cast(uint) samples; 899 } 900 901 // Implements IEditController 902 903 extern(Windows) override tresult setComponentState (IBStream state) 904 { 905 return kNotImplemented; 906 } 907 908 extern(Windows) override tresult setState(IBStream state) 909 { 910 debug(logVST3Client) debugLog(">setState".ptr); 911 debug(logVST3Client) scope(exit) debugLog("<setState".ptr); 912 913 // Manage VST3 state versionning. 914 // First byte of the state is the major VST3 chunk parsing method (we need versionning just in case) 915 ubyte version_; 916 int bytesRead; 917 if (state.read (&version_, 1, &bytesRead) != kResultOk) 918 return kResultFalse; 919 if (version_ != 0) 920 return kResultFalse; // Only version zero is supported 921 922 // (version 0) Second byte of the state is the bypass parameter 923 ubyte bypass; 924 if (state.read (&bypass, 1, &bytesRead) != kResultOk) 925 return kResultFalse; 926 atomicStore(_bypassed, bypass != 0); 927 928 // Try to find current position with seeking to the end 929 int size; 930 { 931 long curPos; 932 if (state.tell(&curPos) != kResultOk) 933 return kResultFalse; 934 935 long newPos; 936 if (state.seek(0, IBStream.kIBSeekEnd, &newPos) != kResultOk) 937 return kResultFalse; 938 939 size = cast(int)(newPos - curPos); 940 941 if (state.seek(curPos, IBStream.kIBSeekSet, null) != kResultOk) 942 return kResultFalse; 943 } 944 945 ubyte[] chunk = mallocSlice!ubyte(size); 946 scope(exit) chunk.freeSlice(); 947 948 if (state.read (chunk.ptr, size, &bytesRead) != kResultOk) 949 return kResultFalse; 950 951 bool err; 952 auto presetBank = _client.presetBank(); 953 presetBank.loadStateChunk(chunk, &err); 954 if (err) 955 return kResultFalse; 956 return kResultTrue; 957 } 958 959 extern(Windows) override tresult getState(IBStream state) 960 { 961 debug(logVST3Client) debugLog(">getState".ptr); 962 debug(logVST3Client) scope(exit) debugLog("<getState".ptr); 963 964 // First byte of the state is the major VST3 chunk parsing method (we need versionning just in case) 965 ubyte CURRENT_VST3_STATE_VERSION = 0; 966 if (state.write(&CURRENT_VST3_STATE_VERSION, 1, null) != kResultTrue) 967 return kResultFalse; 968 969 // (version 0) Second byte of the state is the bypass parameter 970 ubyte bypass = atomicLoad(_bypassed) ? 1 : 0; 971 if (state.write(&bypass, 1, null) != kResultTrue) 972 return kResultFalse; 973 974 auto presetBank = _client.presetBank(); 975 ubyte[] chunk = presetBank.getStateChunkFromCurrentState(); 976 scope(exit) free(chunk.ptr); 977 return state.write(chunk.ptr, cast(int)(chunk.length), null); 978 } 979 980 extern(Windows) override int32 getParameterCount() 981 { 982 // Add 2 because bypass and program change fake parameters 983 int vst3ParamsCount = cast(int)(_client.params.length) + 2; 984 985 version(futureVST3MIDICC) 986 { 987 if (_client.receivesMIDI) 988 vst3ParamsCount += NUM_MIDICC_PARAMETERS; 989 } 990 991 return vst3ParamsCount; 992 } 993 994 extern(Windows) override tresult getParameterInfo (int32 paramIndex, ref ParameterInfo info) 995 { 996 if (paramIndex < 0) 997 return kResultFalse; 998 999 if (paramIndex >= getParameterCount()) 1000 return kResultFalse; 1001 1002 // Is it a fake MIDI CC parameter? 1003 version(futureVST3MIDICC) 1004 { 1005 if (_client.receivesMIDI) 1006 { 1007 int clientParamsPlus2 = cast(int)(_client.params.length) + 2; 1008 if (paramIndex >= clientParamsPlus2 && paramIndex < clientParamsPlus2 + NUM_MIDICC_PARAMETERS) 1009 { 1010 paramIndex -= clientParamsPlus2; 1011 int midiChan = paramIndex / NUM_MIDICC_PER_CHAN; 1012 int cc = paramIndex % NUM_MIDICC_PER_CHAN; 1013 assert(midiChan >= 0 && midiChan < NUM_MIDI_CHANNELS_INPUT); 1014 assert(cc >= 0 && cc <= 129); 1015 1016 info.id = PARAM_ID_MIDICC_START + midiChan * NUM_MIDICC_PER_CHAN + cc; 1017 1018 char[128] nameBuf; 1019 snprintf(nameBuf.ptr, 128, "MIDI CC %d|%d".ptr, midiChan, cc); 1020 str8ToStr16(info.title.ptr, nameBuf.ptr, 128); 1021 str8ToStr16(info.shortTitle.ptr, "".ptr, 128); 1022 str8ToStr16(info.units.ptr, "".ptr, 128); 1023 info.stepCount = 0; // continuous 1024 info.defaultNormalizedValue = 0.0f; 1025 info.unitId = 0; // root, unit 0 is always here 1026 info.flags = 0; 1027 return kResultTrue; 1028 } 1029 } 1030 } 1031 1032 if (paramIndex == 0) 1033 { 1034 info.id = PARAM_ID_BYPASS; 1035 str8ToStr16(info.title.ptr, "Bypass".ptr, 128); 1036 str8ToStr16(info.shortTitle.ptr, "Byp".ptr, 128); 1037 str8ToStr16(info.units.ptr, "".ptr, 128); 1038 info.stepCount = 1; 1039 info.defaultNormalizedValue = 0.0f; 1040 info.unitId = 0; // root, unit 0 is always here 1041 info.flags = ParameterInfo.ParameterFlags.kCanAutomate 1042 | ParameterInfo.ParameterFlags.kIsBypass 1043 | ParameterInfo.ParameterFlags.kIsList; 1044 return kResultTrue; 1045 } 1046 else if (paramIndex == 1) 1047 { 1048 info.id = PARAM_ID_PROGRAM_CHANGE; 1049 str8ToStr16(info.title.ptr, "Preset".ptr, 128); 1050 str8ToStr16(info.shortTitle.ptr, "Pre".ptr, 128); 1051 str8ToStr16(info.units.ptr, "".ptr, 128); 1052 info.stepCount = _presetStepCount; 1053 info.defaultNormalizedValue = 0.0f; 1054 info.unitId = 0; // root, unit 0 is always here 1055 info.flags = ParameterInfo.ParameterFlags.kIsProgramChange; 1056 return kResultTrue; 1057 } 1058 else 1059 { 1060 info.id = convertVST3ParamIndexToParamID(paramIndex); 1061 Parameter param = _client.param(convertParamIDToClientParamIndex(info.id)); 1062 str8ToStr16(info.title.ptr, param.name, 128); 1063 str8ToStr16(info.shortTitle.ptr, param.name(), 128); 1064 str8ToStr16(info.units.ptr, param.label(), 128); 1065 info.stepCount = 0; // continuous 1066 info.defaultNormalizedValue = param.getNormalizedDefault(); 1067 info.unitId = 0; // root, unit 0 is always here 1068 info.flags = 0; 1069 1070 if (param.isAutomatable) 1071 { 1072 info.flags |= ParameterInfo.ParameterFlags.kCanAutomate; 1073 } 1074 return kResultTrue; 1075 } 1076 } 1077 1078 /** Gets for a given paramID and normalized value its associated string representation. */ 1079 extern(Windows) override tresult getParamStringByValue (ParamID id, ParamValue valueNormalized, String128* string_ ) 1080 { 1081 debug(logVST3Client) debugLog(">getParamStringByValue".ptr); 1082 1083 version(futureVST3MIDICC) 1084 { 1085 int midiChan; 1086 int cc; 1087 if (isMIDIInputCCParameter(id, midiChan, cc)) 1088 { 1089 char[128] buf; 1090 snprintf(buf.ptr, 128, "%2.4f".ptr, valueNormalized); 1091 str8ToStr16(string_.ptr, buf.ptr, 128); 1092 return kResultTrue; 1093 } 1094 } 1095 1096 if (id == PARAM_ID_BYPASS) 1097 { 1098 if (valueNormalized < 0.5f) 1099 str8ToStr16(string_.ptr, "No".ptr, 128); 1100 else 1101 str8ToStr16(string_.ptr, "Yes".ptr, 128); 1102 return kResultTrue; 1103 } 1104 1105 if (id == PARAM_ID_PROGRAM_CHANGE) 1106 { 1107 int presetIndex; 1108 if (convertPresetParamToPlain(valueNormalized, &presetIndex)) 1109 { 1110 // Gives back name of preset 1111 str8ToStr16(string_.ptr, _client.presetBank.preset(presetIndex).name, 128); 1112 return kResultTrue; 1113 } 1114 else 1115 { 1116 str8ToStr16(string_.ptr, "None".ptr, 128); 1117 return kResultTrue; 1118 } 1119 } 1120 1121 int paramIndex = convertParamIDToClientParamIndex(id); 1122 if (!_client.isValidParamIndex(paramIndex)) 1123 { 1124 return kResultFalse; 1125 } 1126 1127 if (string_ is null) 1128 return kResultFalse; 1129 1130 Parameter param = _client.param(paramIndex); 1131 char[128] buf; 1132 param.stringFromNormalizedValue(valueNormalized, buf.ptr, 128); 1133 str8ToStr16(string_.ptr, buf.ptr, 128); 1134 1135 debug(logVST3Client) debugLog("<getParamStringByValue".ptr); 1136 return kResultTrue; 1137 } 1138 1139 /** Gets for a given paramID and string its normalized value. */ 1140 extern(Windows) override tresult getParamValueByString (ParamID id, TChar* string_, ref ParamValue valueNormalized ) 1141 { 1142 debug(logVST3Client) debugLog(">getParamValueByString".ptr); 1143 1144 if (id == PARAM_ID_BYPASS || id == PARAM_ID_PROGRAM_CHANGE) 1145 return kResultFalse; // MAYDO, eventually 1146 1147 // Convert short zero-terminated strings from UTF-16 to UTF-8, return length. 1148 // Output has a terminal zero. 1149 static int convertUTF16ToUTF8_128(TChar* input, char[] output) pure nothrow @nogc 1150 { 1151 assert(output.length == 128); 1152 int len = 0; 1153 output[127] = '\0'; 1154 for(int i = 0; i < 128; ++i) 1155 { 1156 // Note: no surrogates supported in this UTF-16 to UTF8 conversion 1157 output[i] = cast(char)input[i]; 1158 if (input[i] == 0) 1159 break; 1160 1161 len++; 1162 } 1163 return len; 1164 } 1165 1166 version(futureVST3MIDICC) 1167 { 1168 int midiChan; 1169 int cc; 1170 if (isMIDIInputCCParameter(id, midiChan, cc)) 1171 { 1172 char[128] valueUTF8; 1173 int len = convertUTF16ToUTF8_128(string_, valueUTF8[]); 1174 bool err; 1175 double parsed = convertStringToDouble(valueUTF8.ptr, false, &err); 1176 if (err) 1177 return kResultFalse; // didn't parse a double 1178 1179 valueNormalized = parsed; 1180 return kResultTrue; 1181 } 1182 } 1183 1184 int paramIndex = convertParamIDToClientParamIndex(id); 1185 if (!_client.isValidParamIndex(paramIndex)) 1186 { 1187 debug(logVST3Client) debugLog("getParamValueByString got a wrong parameter index".ptr); 1188 return kResultFalse; 1189 } 1190 Parameter param = _client.param(paramIndex); 1191 1192 char[128] valueUTF8; 1193 int len = convertUTF16ToUTF8_128(string_, valueUTF8[]); 1194 1195 if (param.normalizedValueFromString( valueUTF8[0..len], valueNormalized)) 1196 { 1197 debug(logVST3Client) scope(exit) debugLog("<getParamValueByString".ptr); 1198 return kResultTrue; 1199 } 1200 else 1201 { 1202 debug(logVST3Client) scope(exit) debugLog("<getParamValueByString".ptr); 1203 return kResultFalse; 1204 } 1205 } 1206 1207 /** Returns for a given paramID and a normalized value its plain representation 1208 (for example 90 for 90db - see \ref vst3AutomationIntro). */ 1209 extern(Windows) override ParamValue normalizedParamToPlain (ParamID id, ParamValue valueNormalized) 1210 { 1211 debug(logVST3Client) debugLog(">normalizedParamToPlain".ptr); 1212 debug(logVST3Client) scope(exit) debugLog("<normalizedParamToPlain".ptr); 1213 1214 int midiChan; 1215 int cc; 1216 if (isMIDIInputCCParameter(id, midiChan, cc)) 1217 { 1218 return valueNormalized; // no change 1219 } 1220 1221 if (id == PARAM_ID_BYPASS) 1222 { 1223 return convertBypassParamToPlain(valueNormalized); 1224 } 1225 else if (id == PARAM_ID_PROGRAM_CHANGE) 1226 { 1227 int presetIndex = 0; 1228 convertPresetParamToPlain(valueNormalized, &presetIndex); 1229 return presetIndex; 1230 } 1231 else 1232 { 1233 int paramIndex = convertParamIDToClientParamIndex(id); 1234 if (!_client.isValidParamIndex(paramIndex)) 1235 return 0; 1236 Parameter param = _client.param(paramIndex); 1237 return valueNormalized; // Note: the host don't need to know we do not deal with normalized values internally 1238 } 1239 } 1240 1241 /** Returns for a given paramID and a plain value its normalized value. (see \ref vst3AutomationIntro) */ 1242 extern(Windows) override ParamValue plainParamToNormalized (ParamID id, ParamValue plainValue) 1243 { 1244 debug(logVST3Client) debugLog(">plainParamToNormalized".ptr); 1245 debug(logVST3Client) scope(exit) debugLog("<plainParamToNormalized".ptr); 1246 1247 int midiChan; 1248 int cc; 1249 if (isMIDIInputCCParameter(id, midiChan, cc)) 1250 { 1251 return plainValue; // no change 1252 } 1253 1254 if (id == PARAM_ID_BYPASS) 1255 { 1256 return convertBypassParamToNormalized(plainValue); 1257 } 1258 else if (id == PARAM_ID_PROGRAM_CHANGE) 1259 { 1260 return convertPresetParamToNormalized(plainValue); 1261 } 1262 else 1263 { 1264 int paramIndex = convertParamIDToClientParamIndex(id); 1265 if (!_client.isValidParamIndex(paramIndex)) 1266 return 0; 1267 Parameter param = _client.param(paramIndex); 1268 return plainValue; // Note: the host don't need to know we do not deal with normalized values internally 1269 } 1270 } 1271 1272 /** Returns the normalized value of the parameter associated to the paramID. */ 1273 extern(Windows) override ParamValue getParamNormalized (ParamID id) 1274 { 1275 debug(logVST3Client) debugLog(">getParamNormalized".ptr); 1276 debug(logVST3Client) scope(exit) debugLog("<getParamNormalized".ptr); 1277 1278 version(futureVST3MIDICC) 1279 { 1280 int midiChan; 1281 int cc; 1282 if (isMIDIInputCCParameter(id, midiChan, cc)) 1283 { 1284 return midiCCValue(midiChan, cc); 1285 } 1286 } 1287 1288 if (id == PARAM_ID_BYPASS) 1289 { 1290 return atomicLoad(_bypassed) ? 1.0f : 0.0f; 1291 } 1292 else if (id == PARAM_ID_PROGRAM_CHANGE) 1293 { 1294 int currentPreset = _client.presetBank.currentPresetIndex(); 1295 return convertPresetParamToNormalized(currentPreset); 1296 } 1297 else 1298 { 1299 int paramIndex = convertParamIDToClientParamIndex(id); 1300 if (!_client.isValidParamIndex(paramIndex)) 1301 return 0; 1302 Parameter param = _client.param(paramIndex); 1303 return param.getForHost(); 1304 } 1305 } 1306 1307 /** Sets the normalized value to the parameter associated to the paramID. The controller must never 1308 pass this value-change back to the host via the IComponentHandler. It should update the according 1309 GUI element(s) only!*/ 1310 extern(Windows) override tresult setParamNormalized (ParamID id, ParamValue value) 1311 { 1312 debug(logVST3Client) debugLog(">setParamNormalized".ptr); 1313 debug(logVST3Client) scope(exit) debugLog("<setParamNormalized".ptr); 1314 1315 version(futureVST3MIDICC) 1316 { 1317 int midiChan; 1318 int cc; 1319 if (isMIDIInputCCParameter(id, midiChan, cc)) 1320 { 1321 midiCCValue(midiChan, cc) = value; 1322 return kResultTrue; 1323 } 1324 } 1325 1326 if (id == PARAM_ID_BYPASS) 1327 { 1328 atomicStore(_bypassed, (value >= 0.5f)); 1329 return kResultTrue; 1330 } 1331 else if (id == PARAM_ID_PROGRAM_CHANGE) 1332 { 1333 int presetIndex; 1334 if (convertPresetParamToPlain(value, &presetIndex)) 1335 { 1336 _client.presetBank.loadPresetFromHost(presetIndex); 1337 } 1338 return kResultTrue; 1339 } 1340 else 1341 { 1342 int paramIndex = convertParamIDToClientParamIndex(id); 1343 if (!_client.isValidParamIndex(paramIndex)) 1344 return kResultFalse; 1345 Parameter param = _client.param(paramIndex); 1346 param.setFromHost(value); 1347 return kResultTrue; 1348 } 1349 } 1350 1351 /** Gets from host a handler. */ 1352 extern(Windows) override tresult setComponentHandler (IComponentHandler handler) 1353 { 1354 debug(logVST3Client) debugLog(">setComponentHandler".ptr); 1355 debug(logVST3Client) scope(exit) debugLog("<setComponentHandler".ptr); 1356 if (_handler is handler) 1357 return kResultTrue; 1358 1359 if (_handler) 1360 { 1361 _handler.release(); 1362 _handler = null; 1363 } 1364 1365 _handler = handler; 1366 if (_handler) 1367 { 1368 _handler.addRef(); 1369 } 1370 return kResultTrue; 1371 } 1372 1373 // view 1374 /** Creates the editor view of the Plug-in, currently only "editor" is supported, see \ref ViewType. 1375 The life time of the editor view will never exceed the life time of this controller instance. */ 1376 extern(Windows) override IPlugView createView (FIDString name) 1377 { 1378 debug(logVST3Client) debugLog(">createView".ptr); 1379 debug(logVST3Client) scope(exit) debugLog("<createView".ptr); 1380 if (!_client.hasGUI) 1381 return null; 1382 if (name !is null && strcmp(name, "editor") == 0) 1383 return mallocNew!DplugView(this); 1384 return null; 1385 } 1386 1387 // implements IEditController2 1388 1389 extern(Windows) override tresult setKnobMode (KnobMode mode) 1390 { 1391 return (mode == kLinearMode) ? kResultTrue : kResultFalse; 1392 } 1393 1394 extern(Windows) override tresult openHelp (TBool onlyCheck) 1395 { 1396 return kResultFalse; 1397 } 1398 1399 extern(Windows) override tresult openAboutBox (TBool onlyCheck) 1400 { 1401 return kResultFalse; 1402 } 1403 1404 // implements IMidiMapping 1405 extern(Windows) override tresult getMidiControllerAssignment(int busIndex, 1406 short channel, 1407 CtrlNumber midiControllerNumber, 1408 ref ParamID id) 1409 { 1410 version(futureVST3MIDICC) 1411 { 1412 if (midiControllerNumber > 129) 1413 return kResultFalse; 1414 1415 if (channel < 0 || channel >= NUM_MIDI_CHANNELS_INPUT) 1416 return kResultFalse; // non-existing channel 1417 1418 id = PARAM_ID_MIDICC_START + midiControllerNumber + channel * NUM_MIDICC_PER_CHAN; 1419 assert(id >= PARAM_ID_MIDICC_START && id < PARAM_ID_MIDICC_STOP); 1420 return kResultTrue; 1421 } 1422 else 1423 { 1424 return kResultFalse; 1425 } 1426 } 1427 1428 1429 // implements IUnitInfo 1430 1431 extern(Windows) override int32 getUnitCount () 1432 { 1433 return 1; 1434 } 1435 1436 /** Gets UnitInfo for a given index in the flat list of unit. */ 1437 extern(Windows) override tresult getUnitInfo (int32 unitIndex, ref UnitInfo info /*out*/) 1438 { 1439 if (unitIndex == 0) 1440 { 1441 info.id = kRootUnitId; 1442 info.parentUnitId = kNoParentUnitId; 1443 str8ToStr16(info.name.ptr, "Root Unit".ptr, 128); 1444 info.programListId = PARAM_ID_PROGRAM_CHANGE; 1445 return kResultTrue; 1446 } 1447 return kResultFalse; 1448 } 1449 1450 /** Component intern program structure. */ 1451 /** Gets the count of Program List. */ 1452 extern(Windows) override int32 getProgramListCount () 1453 { 1454 return 1; 1455 } 1456 1457 /** Gets for a given index the Program List Info. */ 1458 extern(Windows) override tresult getProgramListInfo (int32 listIndex, ref ProgramListInfo info /*out*/) 1459 { 1460 ProgramListInfo result; 1461 result.id = PARAM_ID_PROGRAM_CHANGE; 1462 result.programCount = _client.presetBank().numPresets(); 1463 str8ToStr16(result.name.ptr, "Factory Presets".ptr, 128); 1464 info = result; 1465 return kResultTrue; 1466 } 1467 1468 /** Gets for a given program list ID and program index its program name. */ 1469 extern(Windows) override tresult getProgramName (ProgramListID listId, int32 programIndex, String128* name /*out*/) 1470 { 1471 if (listId != PARAM_ID_PROGRAM_CHANGE) 1472 return kResultFalse; 1473 auto presetBank = _client.presetBank(); 1474 if (!presetBank.isValidPresetIndex(programIndex)) 1475 return kResultFalse; 1476 str8ToStr16((*name).ptr, presetBank.preset(programIndex).name, 128); 1477 return kResultTrue; 1478 } 1479 1480 /** Gets for a given program list ID, program index and attributeId the associated attribute value. */ 1481 extern(Windows) override tresult getProgramInfo (ProgramListID listId, int32 programIndex, 1482 const(wchar)* attributeId /*in*/, String128* attributeValue /*out*/) 1483 { 1484 return kNotImplemented; // I don't understand what these "attributes" could be 1485 } 1486 1487 /** Returns kResultTrue if the given program index of a given program list ID supports PitchNames. */ 1488 extern(Windows) override tresult hasProgramPitchNames (ProgramListID listId, int32 programIndex) 1489 { 1490 return kResultFalse; 1491 } 1492 1493 /** Gets the PitchName for a given program list ID, program index and pitch. 1494 If PitchNames are changed the Plug-in should inform the host with IUnitHandler::notifyProgramListChange. */ 1495 extern(Windows) override tresult getProgramPitchName (ProgramListID listId, int32 programIndex, 1496 int16 midiPitch, String128* name /*out*/) 1497 { 1498 return kResultFalse; 1499 } 1500 1501 // units selection -------------------- 1502 /** Gets the current selected unit. */ 1503 extern(Windows) override UnitID getSelectedUnit () 1504 { 1505 return 0; 1506 } 1507 1508 /** Sets a new selected unit. */ 1509 extern(Windows) override tresult selectUnit (UnitID unitId) 1510 { 1511 return (unitId == 0) ? kResultTrue : kResultFalse; 1512 } 1513 1514 /** Gets the according unit if there is an unambiguous relation between a channel or a bus and a unit. 1515 This method mainly is intended to find out which unit is related to a given MIDI input channel. */ 1516 extern(Windows) override tresult getUnitByBus (MediaType type, BusDirection dir, int32 busIndex, 1517 int32 channel, ref UnitID unitId /*out*/) 1518 { 1519 unitId = 0; 1520 return kResultTrue; 1521 } 1522 1523 /** Receives a preset data stream. 1524 - If the component supports program list data (IProgramListData), the destination of the data 1525 stream is the program specified by list-Id and program index (first and second parameter) 1526 - If the component supports unit data (IUnitData), the destination is the unit specified by the first 1527 parameter - in this case parameter programIndex is < 0). */ 1528 extern(Windows) tresult setUnitProgramData (int32 listOrUnitId, int32 programIndex, IBStream data) 1529 { 1530 return kNotImplemented; 1531 } 1532 1533 private: 1534 Client _client; 1535 IComponentHandler _handler; 1536 IHostCommand _hostCommand; 1537 1538 // Assigned when UI is opened, nulled when closed. This allow to request a host parent window resize. 1539 IPlugFrame _plugFrame = null; 1540 IPlugView _currentView = null; 1541 1542 shared(bool) _shouldInitialize = true; 1543 shared(bool) _bypassed = false; 1544 1545 // This is number of preset - 1, but 0 if no presets 1546 // "stepcount" is offset by 1 in VST3 Parameter parlance 1547 // stepcount = 1 gives 2 different values 1548 int _presetStepCount; 1549 1550 // Number of Client parameters (the ones the Dplug users sees). 1551 // VST3 adds 2 hidden ones (+ 130 x 16 in case of MIDI input) 1552 int _numParams; 1553 1554 float _sampleRate; 1555 shared(float) _sampleRateHostPOV = 44100.0f; 1556 shared(float) _sampleRateAudioThreadPOV = 44100.0f; 1557 float _sampleRateDSPPOV = 0.0f; 1558 1559 shared(int) _maxSamplesPerBlockHostPOV = -1; 1560 int _maxSamplesPerBlockDSPPOV = -1; 1561 1562 int _numInputChannels = -1; /// Number of input channels from the DSP point of view 1563 int _numOutputChannels = -1; /// Number of output channels from the DSP point of view 1564 1565 float*[] _inputPointers; 1566 float*[] _outputPointers; 1567 1568 DAW _daw = DAW.Unknown; 1569 char[128] _hostName; 1570 1571 TimeInfo _timeInfo; 1572 1573 static struct Bus 1574 { 1575 bool active; 1576 SpeakerArrangement speakerArrangement; 1577 BusInfo info; 1578 } 1579 1580 Vec!Bus _audioInputs; 1581 Vec!Bus _audioOutputs; 1582 Vec!Bus _eventInputs; 1583 Vec!Bus _eventOutputs; 1584 1585 Vec!Bus* getBusList(MediaType type, BusDirection dir) 1586 { 1587 if (type == kAudio) 1588 { 1589 if (dir == kInput) return &_audioInputs; 1590 if (dir == kOutput) return &_audioOutputs; 1591 } 1592 else if (type == kEvent) 1593 { 1594 if (dir == kInput) return &_eventInputs; 1595 if (dir == kOutput) return &_eventOutputs; 1596 } 1597 return null; 1598 } 1599 1600 // Scratch buffers 1601 float[] _zeroesBuffer; // for deactivated input bus 1602 float[] _outputScratchBuffer; // for deactivated output bus 1603 Vec!float[] _inputScratchBuffers; // for input safe copy 1604 int _maxInputs; 1605 1606 void resizeScratchBuffers(int maxFrames) nothrow @nogc 1607 { 1608 for (int i = 0; i < _maxInputs; ++i) 1609 _inputScratchBuffers[i].resize(maxFrames); 1610 _outputScratchBuffer.reallocBuffer(maxFrames); 1611 _zeroesBuffer.reallocBuffer(maxFrames); 1612 _zeroesBuffer[0..maxFrames] = 0; 1613 } 1614 1615 // host application reference 1616 IHostApplication _hostApplication = null; 1617 1618 Vec!ParameterTracks _tracks; 1619 Vec!double _sharedValues; 1620 1621 // Workaround a VSTHost bug, see Issue #583. 1622 // VSTHost send parameters updates to inexisting parameters. 1623 bool _hostMaySendBadParameterIDs = false; 1624 1625 version(futureVST3MIDICC) 1626 { 1627 // Contains midichannel x 130 values. 1628 Vec!double _midiCCValues; 1629 1630 void initializeMIDICCValues() 1631 { 1632 if (!_client.receivesMIDI) 1633 return; // No need for that state if no MIDI input. 1634 1635 _midiCCValues.resize(NUM_MIDICC_PER_CHAN * NUM_MIDI_CHANNELS_INPUT); 1636 _midiCCValues.fill(0.0f); 1637 } 1638 1639 ref double midiCCValue(int midiChan, int cc) 1640 { 1641 assert(cc >= 0 && cc < NUM_MIDICC_PER_CHAN); 1642 assert(midiChan >= 0 && midiChan < NUM_MIDI_CHANNELS_INPUT); 1643 return _midiCCValues[NUM_MIDICC_PER_CHAN * midiChan + cc]; 1644 } 1645 } 1646 1647 void setHostApplication(FUnknown context) 1648 { 1649 debug(logVST3Client) debugLog(">setHostApplication".ptr); 1650 debug(logVST3Client) scope(exit) debugLog("<setHostApplication".ptr); 1651 IHostApplication hostApplication = null; 1652 if (context.queryInterface(IHostApplication.iid, cast(void**)(&hostApplication)) != kResultOk) 1653 hostApplication = null; 1654 1655 // clean-up _hostApplication former if any 1656 if (_hostApplication !is null) 1657 { 1658 _hostApplication.release(); 1659 _hostApplication = null; 1660 } 1661 1662 if (hostApplication !is null) 1663 { 1664 hostApplication.addRef(); 1665 _hostApplication = hostApplication; 1666 1667 // Identify host 1668 String128 name; 1669 if (_hostApplication.getName(&name) == kResultOk) 1670 { 1671 str16ToStr8(_hostName.ptr, name.ptr, 128); 1672 1673 // Force lowercase 1674 for (int n = 0; n < 128; ++n) 1675 { 1676 if (_hostName[n] >= 'A' && _hostName[n] <= 'Z') 1677 _hostName[n] += ('a' - 'A'); 1678 } 1679 1680 _daw = identifyDAW(_hostName.ptr); 1681 _hostMaySendBadParameterIDs = (_daw == DAW.VSTHost); 1682 } 1683 } 1684 } 1685 1686 double convertBypassParamToNormalized(double plainValue) const 1687 { 1688 return plainValue / cast(double)1; 1689 } 1690 1691 double convertBypassParamToPlain(double normalizedValue) const 1692 { 1693 double v = cast(int)(normalizedValue * 2); 1694 if (v > 1) 1695 v = 1; 1696 return v; 1697 } 1698 1699 double convertPresetParamToNormalized(double presetPlainValue) const 1700 { 1701 if (_presetStepCount == 0) // 0 or 1 preset 1702 return 0; 1703 return presetPlainValue / cast(double)_presetStepCount; 1704 } 1705 1706 bool convertPresetParamToPlain(double presetNormalizedValue, int* presetIndex) 1707 { 1708 int index = cast(int)(presetNormalizedValue * (_presetStepCount + 1)); 1709 if (index > _presetStepCount) 1710 index = _presetStepCount; 1711 *presetIndex = index; 1712 return _client.presetBank.isValidPresetIndex(_presetStepCount); 1713 } 1714 1715 // Helper to get quickly a MIDI cc with its channel, from a ParamID 1716 bool isMIDIInputCCParameter(ParamID id, out int midiChan, out int ccIndex) 1717 { 1718 version(futureVST3MIDICC) 1719 { 1720 if (!_client.receivesMIDI) 1721 return false; 1722 1723 if (id < PARAM_ID_MIDICC_START || id > PARAM_ID_MIDICC_STOP) 1724 return false; 1725 1726 int index = id - PARAM_ID_MIDICC_START; 1727 midiChan = index / NUM_MIDICC_PER_CHAN; 1728 ccIndex = index % NUM_MIDICC_PER_CHAN; 1729 assert(midiChan >= 0 && midiChan < NUM_MIDI_CHANNELS_INPUT); 1730 return true; 1731 } 1732 else 1733 { 1734 return false; 1735 } 1736 } 1737 } 1738 1739 private: 1740 nothrow: 1741 pure: 1742 @nogc: 1743 1744 enum int PARAM_ID_BYPASS = 998; 1745 enum int PARAM_ID_PROGRAM_CHANGE = 999; 1746 1747 version(futureVST3MIDICC) 1748 { 1749 // FUTURE: deprecate those, to make number of input MIDI channels tunable. 1750 enum int NUM_MIDI_CHANNELS_INPUT = 16; 1751 enum int NUM_MIDICC_PARAMETERS = 130 * NUM_MIDI_CHANNELS_INPUT; 1752 enum int NUM_MIDICC_PER_CHAN = 130; 1753 enum int PARAM_ID_MIDICC_START = 1835232512; 1754 enum int PARAM_ID_MIDICC_STOP = PARAM_ID_MIDICC_START + NUM_MIDICC_PARAMETERS; 1755 } 1756 1757 // Convert from VST3 index to VST3 ParamID 1758 int convertVST3ParamIndexToParamID(int index) 1759 { 1760 // Parameter with VST3 index 0 is a fake Bypass parameter 1761 if (index == 0) 1762 return PARAM_ID_BYPASS; 1763 1764 // Parameter with VST3 index 1 is a fake Program Change parameter 1765 if (index == 1) 1766 return PARAM_ID_PROGRAM_CHANGE; 1767 1768 // Parameter with VST3 index 2 is the first client Parameter 1769 return index - 2; 1770 } 1771 1772 // Convert from VST3 ParamID to VST3 index 1773 int convertParamIDToVST3ParamIndex(int index) 1774 { 1775 // Parameter with VST3 index 0 is a fake Bypass parameter 1776 if (index == PARAM_ID_BYPASS) 1777 return 0; 1778 1779 // Parameter with VST3 index 1 is a fake Program Change parameter 1780 if (index == PARAM_ID_PROGRAM_CHANGE) 1781 return 1; 1782 1783 // Parameter with VST3 index 2 is the first client Parameter 1784 return index + 2; 1785 } 1786 1787 /// Convert from VST3 ParamID to Client Parameter index 1788 int convertParamIDToClientParamIndex(int paramID) 1789 { 1790 // Shouldn't be here if this is a fake parameter 1791 assert (paramID != PARAM_ID_BYPASS); 1792 assert (paramID != PARAM_ID_PROGRAM_CHANGE); 1793 1794 // Parameter with VST3 index 2 is the first client Parameter 1795 return paramID; 1796 } 1797 1798 /// Convert from Client Parameter index to VST3 ParamID 1799 int convertClientParamIndexToParamID(int clientIndex) 1800 { 1801 return clientIndex; 1802 } 1803 1804 class DplugView : IPlugView 1805 { 1806 public: 1807 nothrow: 1808 @nogc: 1809 1810 this(VST3Client vst3Client) 1811 { 1812 _vst3Client = vst3Client; 1813 _graphicsMutex = makeMutex(); 1814 } 1815 1816 ~this() 1817 { 1818 } 1819 1820 // Implements FUnknown 1821 mixin QUERY_INTERFACE_SPECIAL_CASE_IUNKNOWN!(IPlugView); 1822 mixin IMPLEMENT_REFCOUNT; 1823 1824 // IPlugView 1825 1826 /** Is Platform UI Type supported 1827 \param type : IDString of \ref platformUIType */ 1828 // MAYDO: there is considerable coupling with dplug:window here. 1829 extern(Windows) override tresult isPlatformTypeSupported (FIDString type) 1830 { 1831 debug(logVST3Client) debugLog(">isPlatformTypeSupported".ptr); 1832 debug(logVST3Client) scope(exit) debugLog("<isPlatformTypeSupported".ptr); 1833 GraphicsBackend backend; 1834 if (convertPlatformToGraphicsBackend(type, &backend)) 1835 return isGraphicsBackendSupported(backend) ? kResultTrue : kResultFalse; 1836 return kResultFalse; 1837 } 1838 1839 /** The parent window of the view has been created, the (platform) representation of the view 1840 should now be created as well. 1841 Note that the parent is owned by the caller and you are not allowed to alter it in any way 1842 other than adding your own views. 1843 Note that in this call the Plug-in could call a IPlugFrame::resizeView ()! 1844 \param parent : platform handle of the parent window or view 1845 \param type : \ref platformUIType which should be created */ 1846 extern(Windows) tresult attached (void* parent, FIDString type) 1847 { 1848 debug(logVST3Client) debugLog(">attached".ptr); 1849 debug(logVST3Client) scope(exit) debugLog("<attached".ptr); 1850 1851 if (_vst3Client._client.hasGUI() ) 1852 { 1853 if (kResultTrue != isPlatformTypeSupported(type)) 1854 return kResultFalse; 1855 1856 GraphicsBackend backend = GraphicsBackend.autodetect; 1857 if (!convertPlatformToGraphicsBackend(type, &backend)) 1858 return kResultFalse; 1859 1860 _graphicsMutex.lock(); 1861 scope(exit) _graphicsMutex.unlock(); 1862 1863 _vst3Client._client.openGUI(parent, null, cast(GraphicsBackend)backend); 1864 return kResultTrue; 1865 } 1866 return kResultFalse; 1867 1868 } 1869 1870 /** The parent window of the view is about to be destroyed. 1871 You have to remove all your own views from the parent window or view. */ 1872 extern(Windows) tresult removed () 1873 { 1874 debug(logVST3Client) debugLog(">removed".ptr); 1875 1876 if (_vst3Client._client.hasGUI() ) 1877 { 1878 _graphicsMutex.lock(); 1879 scope(exit) _graphicsMutex.unlock(); 1880 _vst3Client._plugFrame = null; 1881 _vst3Client._currentView = null; 1882 _vst3Client._client.closeGUI(); 1883 debug(logVST3Client) debugLog("<removed".ptr); 1884 return kResultTrue; 1885 } 1886 return kResultFalse; 1887 } 1888 1889 /** Handling of mouse wheel. */ 1890 extern(Windows) tresult onWheel (float distance) 1891 { 1892 debug(logVST3Client) debugLog(">onWheel".ptr); 1893 debug(logVST3Client) scope(exit) debugLog("<onWheel".ptr); 1894 return kResultFalse; 1895 } 1896 1897 /** Handling of keyboard events : Key Down. 1898 \param key : unicode code of key 1899 \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h 1900 \param modifiers : any combination of modifiers - see \ref KeyModifier in keycodes.h 1901 \return kResultTrue if the key is handled, otherwise kResultFalse. \n 1902 <b> Please note that kResultTrue must only be returned if the key has really been 1903 handled. </b> Otherwise key command handling of the host might be blocked! */ 1904 extern(Windows) tresult onKeyDown (char16 key, int16 keyCode, int16 modifiers) 1905 { 1906 debug(logVST3Client) debugLog(">onKeyDown".ptr); 1907 debug(logVST3Client) scope(exit) debugLog("<onKeyDown".ptr); 1908 return kResultFalse; 1909 } 1910 1911 /** Handling of keyboard events : Key Up. 1912 \param key : unicode code of key 1913 \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h 1914 \param modifiers : any combination of KeyModifier - see \ref KeyModifier in keycodes.h 1915 \return kResultTrue if the key is handled, otherwise return kResultFalse. */ 1916 extern(Windows) tresult onKeyUp (char16 key, int16 keyCode, int16 modifiers) 1917 { 1918 debug(logVST3Client) debugLog(">onKeyUp".ptr); 1919 debug(logVST3Client) scope(exit) debugLog("<onKeyUp".ptr); 1920 return kResultFalse; 1921 } 1922 1923 /** Returns the size of the platform representation of the view. */ 1924 extern(Windows) tresult getSize (ViewRect* size) 1925 { 1926 debug(logVST3Client) debugLog(">getSize".ptr); 1927 debug(logVST3Client) scope(exit) debugLog("<getSize".ptr); 1928 if (!_vst3Client._client.hasGUI()) 1929 return kResultFalse; 1930 1931 _graphicsMutex.lock(); 1932 scope(exit) _graphicsMutex.unlock(); 1933 1934 int widthLogicalPixels, heightLogicalPixels; 1935 if (_vst3Client._client.getGUISize(&widthLogicalPixels, &heightLogicalPixels)) 1936 { 1937 size.left = 0; 1938 size.top = 0; 1939 size.right = widthLogicalPixels; 1940 size.bottom = heightLogicalPixels; 1941 return kResultTrue; 1942 } 1943 return kResultFalse; 1944 } 1945 1946 /** Resizes the platform representation of the view to the given rect. Note that if the Plug-in 1947 * requests a resize (IPlugFrame::resizeView ()) onSize has to be called afterward. */ 1948 extern(Windows) tresult onSize (ViewRect* newSize) 1949 { 1950 _graphicsMutex.lock(); 1951 scope(exit) _graphicsMutex.unlock(); 1952 1953 auto graphics = _vst3Client._client.getGraphics(); 1954 assert(graphics !is null); 1955 1956 graphics.nativeWindowResize(newSize.getWidth(), newSize.getHeight()); // Tell the IWindow to position itself at newSize. 1957 1958 return kResultOk; 1959 } 1960 1961 /** Focus changed message. */ 1962 extern(Windows) tresult onFocus (TBool state) 1963 { 1964 return kResultOk; 1965 } 1966 1967 /** Sets IPlugFrame object to allow the Plug-in to inform the host about resizing. */ 1968 extern(Windows) tresult setFrame (IPlugFrame frame) 1969 { 1970 _vst3Client._plugFrame = frame; 1971 _vst3Client._currentView = cast(IPlugView)this; 1972 return kResultTrue; 1973 } 1974 1975 /** Is view sizable by user. */ 1976 extern(Windows) tresult canResize () 1977 { 1978 _graphicsMutex.lock(); 1979 scope(exit) _graphicsMutex.unlock(); 1980 auto graphics = _vst3Client._client.getGraphics(); 1981 assert(graphics !is null); 1982 1983 tresult result = graphics.isResizeable() ? kResultTrue : kResultFalse; 1984 return result; 1985 } 1986 1987 /** On live resize this is called to check if the view can be resized to the given rect, if not 1988 * adjust the rect to the allowed size. */ 1989 extern(Windows) tresult checkSizeConstraint (ViewRect* rect) 1990 { 1991 _graphicsMutex.lock(); 1992 scope(exit) _graphicsMutex.unlock(); 1993 auto graphics = _vst3Client._client.getGraphics(); 1994 assert(graphics !is null); 1995 1996 int W = rect.getWidth(); 1997 int H = rect.getHeight(); 1998 1999 DAW daw = _vst3Client._hostCommand.getDAW(); 2000 bool reaper = (daw == DAW.Reaper); 2001 bool avoidLargerSizeInCheckConstraint = reaper; 2002 2003 // Force the default size on first opening in REAPER. 2004 // See Issue #559. 2005 // This is because REAPER will try another size for no apparent reason, in VST3 + (Linux or macOS). 2006 bool avoidLiveResize = false; 2007 version(linux) 2008 if (reaper) avoidLiveResize = true; 2009 version(OSX) 2010 if (reaper) avoidLiveResize = true; 2011 if (avoidLiveResize) 2012 { 2013 graphics.getGUISize(&W, &H); 2014 } 2015 else if (avoidLargerSizeInCheckConstraint) 2016 { 2017 // REAPER want instead the max size that fits. 2018 graphics.getMaxSmallerValidSize(&W, &H); 2019 } 2020 else 2021 { 2022 // Most other host accomodate with a larger size. 2023 // Note: It is important to take the nearest here rather than the max size that fits, but is smaller? 2024 // it is to allow an upsize when dragging only one size of the window. 2025 // Else, the window can only be shrink in VST3 hosts (tested in Cubase, FL, Waveform, the test host...) unless the 2026 // window corner is used. 2027 // FUTURE: allow any size, since GUIGraphics does the best it can. 2028 // Check it on Studio One! which is more demanding. 2029 graphics.getNearestValidSize(&W, &H); 2030 } 2031 2032 rect.right = rect.left + W; 2033 rect.bottom = rect.top + H; 2034 2035 return kResultTrue; 2036 } 2037 2038 private: 2039 VST3Client _vst3Client; 2040 UncheckedMutex _graphicsMutex; 2041 2042 static bool convertPlatformToGraphicsBackend(FIDString type, GraphicsBackend* backend) 2043 { 2044 if (strcmp(type, kPlatformTypeHWND.ptr) == 0) 2045 { 2046 *backend = GraphicsBackend.win32; 2047 return true; 2048 } 2049 if (strcmp(type, kPlatformTypeNSView.ptr) == 0) 2050 { 2051 *backend = GraphicsBackend.cocoa; 2052 return true; 2053 } 2054 if (strcmp(type, kPlatformTypeX11EmbedWindowID.ptr) == 0) 2055 { 2056 *backend = GraphicsBackend.x11; 2057 return true; 2058 } 2059 return false; 2060 } 2061 } 2062 2063 2064 2065 // Host commands 2066 2067 class VST3HostCommand : IHostCommand 2068 { 2069 public: 2070 nothrow: 2071 @nogc: 2072 2073 this(VST3Client vst3Client) 2074 { 2075 _vst3Client = vst3Client; 2076 } 2077 2078 override void beginParamEdit(int paramIndex) 2079 { 2080 auto handler = _vst3Client._handler; 2081 if (handler) 2082 handler.beginEdit(convertClientParamIndexToParamID(paramIndex)); 2083 } 2084 2085 override void paramAutomate(int paramIndex, float value) 2086 { 2087 auto handler = _vst3Client._handler; 2088 if (handler) 2089 handler.performEdit(convertClientParamIndexToParamID(paramIndex), value); 2090 } 2091 2092 override void endParamEdit(int paramIndex) 2093 { 2094 auto handler = _vst3Client._handler; 2095 if (handler) 2096 handler.endEdit(convertClientParamIndexToParamID(paramIndex)); 2097 } 2098 2099 override bool requestResize(int width, int height) 2100 { 2101 IPlugFrame frame = _vst3Client._plugFrame; 2102 IPlugView view = _vst3Client._currentView; 2103 if (frame is null || view is null) 2104 return false; 2105 ViewRect rect; 2106 rect.left = 0; 2107 rect.top = 0; 2108 rect.right = width; 2109 rect.bottom = height; 2110 return frame.resizeView(view, &rect) == kResultOk; 2111 } 2112 2113 override bool notifyResized() 2114 { 2115 return false; 2116 } 2117 2118 DAW getDAW() 2119 { 2120 return _vst3Client._daw; 2121 } 2122 2123 override PluginFormat getPluginFormat() 2124 { 2125 return PluginFormat.vst3; 2126 } 2127 2128 private: 2129 VST3Client _vst3Client; 2130 } 2131 2132 /// Returns: `true` if that graphics backend is supported on this platform in VST3 2133 private bool isGraphicsBackendSupported(GraphicsBackend backend) nothrow @nogc 2134 { 2135 version(Windows) 2136 return (backend == GraphicsBackend.win32); 2137 else version(OSX) 2138 { 2139 return (backend == GraphicsBackend.cocoa); 2140 } 2141 else version(linux) 2142 return (backend == GraphicsBackend.x11); 2143 else 2144 static assert(false, "Unsupported OS"); 2145 } 2146 2147 // Store changes of a single parameter over the current buffer. 2148 struct ParameterTracks 2149 { 2150 ParamID id; 2151 double* values; // values for each split sub-buffer, plus the final value. This points into a buffer shared across parameters. 2152 }