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