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