1 /*
2 * Carla SFZero Plugin
3 * Copyright (C) 2018-2020 Filipe Coelho <falktx@falktx.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16 */
17
18 #include "CarlaPluginInternal.hpp"
19 #include "CarlaBackendUtils.hpp"
20 #include "CarlaEngine.hpp"
21
22 #include "sfzero/SFZero.h"
23
24 #include "water/buffers/AudioSampleBuffer.h"
25 #include "water/files/File.h"
26 #include "water/midi/MidiMessage.h"
27
28 using water::AudioSampleBuffer;
29 using water::File;
30 using water::MidiMessage;
31 using water::String;
32
33 // -----------------------------------------------------------------------
34
35 CARLA_BACKEND_START_NAMESPACE
36
37 // -------------------------------------------------------------------------------------------------------------------
38 // Fallback data
39
40 static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 };
41
42
loadingIdleCallbackFunction(void * ptr)43 static void loadingIdleCallbackFunction(void* ptr)
44 {
45 ((CarlaEngine*)ptr)->callback(true, false, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr);
46 }
47
48 // -------------------------------------------------------------------------------------------------------------------
49
50 class CarlaPluginSFZero : public CarlaPlugin
51 {
52 public:
CarlaPluginSFZero(CarlaEngine * const engine,const uint id)53 CarlaPluginSFZero(CarlaEngine* const engine, const uint id)
54 : CarlaPlugin(engine, id),
55 fSynth(),
56 fNumVoices(0.0f),
57 fLabel(nullptr),
58 fRealName(nullptr)
59 {
60 carla_debug("CarlaPluginSFZero::CarlaPluginSFZero(%p, %i)", engine, id);
61 }
62
~CarlaPluginSFZero()63 ~CarlaPluginSFZero() override
64 {
65 carla_debug("CarlaPluginSFZero::~CarlaPluginSFZero()");
66
67 pData->singleMutex.lock();
68 pData->masterMutex.lock();
69
70 if (pData->client != nullptr && pData->client->isActive())
71 pData->client->deactivate(true);
72
73 if (pData->active)
74 {
75 deactivate();
76 pData->active = false;
77 }
78
79 if (fLabel != nullptr)
80 {
81 delete[] fLabel;
82 fLabel = nullptr;
83 }
84
85 if (fRealName != nullptr)
86 {
87 delete[] fRealName;
88 fRealName = nullptr;
89 }
90
91 clearBuffers();
92 }
93
94 // -------------------------------------------------------------------
95 // Information (base)
96
getType() const97 PluginType getType() const noexcept override
98 {
99 return PLUGIN_SFZ;
100 }
101
getCategory() const102 PluginCategory getCategory() const noexcept override
103 {
104 return PLUGIN_CATEGORY_SYNTH;
105 }
106
107 // -------------------------------------------------------------------
108 // Information (count)
109
110 // nothing
111
112 // -------------------------------------------------------------------
113 // Information (current data)
114
115 // nothing
116
117 // -------------------------------------------------------------------
118 // Information (per-plugin data)
119
getOptionsAvailable() const120 uint getOptionsAvailable() const noexcept override
121 {
122 uint options = 0x0;
123
124 options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
125 options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
126 options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
127 options |= PLUGIN_OPTION_SEND_PITCHBEND;
128 options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
129 options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
130
131 return options;
132 }
133
getParameterValue(const uint32_t parameterId) const134 float getParameterValue(const uint32_t parameterId) const noexcept override
135 {
136 CARLA_SAFE_ASSERT_RETURN(parameterId == 0, 0.0f);
137
138 return fNumVoices;
139 }
140
getLabel(char * const strBuf) const141 bool getLabel(char* const strBuf) const noexcept override
142 {
143 if (fLabel != nullptr)
144 {
145 std::strncpy(strBuf, fLabel, STR_MAX);
146 return true;
147 }
148
149 return CarlaPlugin::getLabel(strBuf);
150 }
151
getMaker(char * const strBuf) const152 bool getMaker(char* const strBuf) const noexcept override
153 {
154 std::strncpy(strBuf, "SFZero engine", STR_MAX);
155 return true;
156 }
157
getCopyright(char * const strBuf) const158 bool getCopyright(char* const strBuf) const noexcept override
159 {
160 std::strncpy(strBuf, "ISC", STR_MAX);
161 return true;
162 }
163
getRealName(char * const strBuf) const164 bool getRealName(char* const strBuf) const noexcept override
165 {
166 if (fRealName != nullptr)
167 {
168 std::strncpy(strBuf, fRealName, STR_MAX);
169 return true;
170 }
171
172 return CarlaPlugin::getRealName(strBuf);
173 }
174
getParameterName(const uint32_t parameterId,char * const strBuf) const175 bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override
176 {
177 CARLA_SAFE_ASSERT_RETURN(parameterId == 0, false);
178
179 std::strncpy(strBuf, "Voice Count", STR_MAX);
180 return true;
181 }
182
183 // -------------------------------------------------------------------
184 // Set data (state)
185
186 // nothing
187
188 // -------------------------------------------------------------------
189 // Set data (internal stuff)
190
191 // nothing
192
193 // -------------------------------------------------------------------
194 // Set data (plugin-specific stuff)
195
196 // nothing
197
198 // -------------------------------------------------------------------
199 // Set ui stuff
200
201 // nothing
202
203 // -------------------------------------------------------------------
204 // Plugin state
205
reload()206 void reload() override
207 {
208 CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,);
209 carla_debug("CarlaPluginSFZero::reload() - start");
210
211 const EngineProcessMode processMode(pData->engine->getProccessMode());
212
213 // Safely disable plugin for reload
214 const ScopedDisabler sd(this);
215
216 if (pData->active)
217 deactivate();
218
219 clearBuffers();
220
221 pData->audioOut.createNew(2);
222 pData->param.createNew(1, false);
223
224 const uint portNameSize(pData->engine->getMaxPortNameSize());
225 CarlaString portName;
226
227 // ---------------------------------------
228 // Audio Outputs
229
230 // out-left
231 portName.clear();
232
233 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
234 {
235 portName = pData->name;
236 portName += ":";
237 }
238
239 portName += "out-left";
240 portName.truncate(portNameSize);
241
242 pData->audioOut.ports[0].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 0);
243 pData->audioOut.ports[0].rindex = 0;
244
245 // out-right
246 portName.clear();
247
248 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
249 {
250 portName = pData->name;
251 portName += ":";
252 }
253
254 portName += "out-right";
255 portName.truncate(portNameSize);
256
257 pData->audioOut.ports[1].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 1);
258 pData->audioOut.ports[1].rindex = 1;
259
260 // ---------------------------------------
261 // Event Input
262
263 portName.clear();
264
265 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
266 {
267 portName = pData->name;
268 portName += ":";
269 }
270
271 portName += "events-in";
272 portName.truncate(portNameSize);
273
274 pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0);
275
276 // ---------------------------------------
277 // Parameters
278
279 pData->param.data[0].type = PARAMETER_OUTPUT;
280 pData->param.data[0].hints = PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE | PARAMETER_IS_INTEGER;
281 pData->param.data[0].index = 0;
282 pData->param.data[0].rindex = 0;
283 pData->param.ranges[0].min = 0.0f;
284 pData->param.ranges[0].max = 128;
285 pData->param.ranges[0].def = 0.0f;
286 pData->param.ranges[0].step = 1.0f;
287 pData->param.ranges[0].stepSmall = 1.0f;
288 pData->param.ranges[0].stepLarge = 1.0f;
289
290 // ---------------------------------------
291
292 // plugin hints
293 pData->hints = 0x0;
294 pData->hints |= PLUGIN_IS_SYNTH;
295 pData->hints |= PLUGIN_CAN_VOLUME;
296 pData->hints |= PLUGIN_CAN_BALANCE;
297
298 // extra plugin hints
299 pData->extraHints = 0x0;
300 pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN;
301
302 bufferSizeChanged(pData->engine->getBufferSize());
303 reloadPrograms(true);
304
305 if (pData->active)
306 activate();
307
308 carla_debug("CarlaPluginSFZero::reload() - end");
309 }
310
311 // -------------------------------------------------------------------
312 // Plugin processing
313
process(const float * const * const,float ** const audioOut,const float * const *,float **,const uint32_t frames)314 void process(const float* const* const, float** const audioOut, const float* const*, float**, const uint32_t frames) override
315 {
316 // --------------------------------------------------------------------------------------------------------
317 // Check if active
318
319 if (! pData->active)
320 {
321 // disable any output sound
322 for (uint32_t i=0; i < pData->audioOut.count; ++i)
323 carla_zeroFloats(audioOut[i], frames);
324
325 fNumVoices = 0.0f;
326 return;
327 }
328
329 // --------------------------------------------------------------------------------------------------------
330 // Check if needs reset
331
332 if (pData->needsReset)
333 {
334 fSynth.allNotesOff(0, false);
335 pData->needsReset = false;
336 }
337
338 // --------------------------------------------------------------------------------------------------------
339 // Event Input and Processing
340
341 {
342 // ----------------------------------------------------------------------------------------------------
343 // Setup audio buffer
344
345 AudioSampleBuffer audioOutBuffer(audioOut, 2, frames);
346
347 // ----------------------------------------------------------------------------------------------------
348 // MIDI Input (External)
349
350 if (pData->extNotes.mutex.tryLock())
351 {
352 for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next())
353 {
354 const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback));
355 CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS);
356
357 if (note.velo > 0)
358 fSynth.noteOn(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f);
359 else
360 fSynth.noteOff(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f, true);
361 }
362
363 pData->extNotes.data.clear();
364 pData->extNotes.mutex.unlock();
365
366 } // End of MIDI Input (External)
367
368 // ----------------------------------------------------------------------------------------------------
369 // Event Input (System)
370
371 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
372 bool allNotesOffSent = false;
373 #endif
374 uint32_t timeOffset = 0;
375
376 for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
377 {
378 const EngineEvent& event(pData->event.portIn->getEvent(i));
379
380 uint32_t eventTime = event.time;
381 CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime < frames, eventTime, frames);
382
383 if (eventTime < timeOffset)
384 {
385 carla_stderr2("Timing error, eventTime:%u < timeOffset:%u for '%s'",
386 eventTime, timeOffset, pData->name);
387 eventTime = timeOffset;
388 }
389 else if (eventTime > timeOffset)
390 {
391 if (processSingle(audioOutBuffer, eventTime - timeOffset, timeOffset))
392 timeOffset = eventTime;
393 }
394
395 // Control change
396 switch (event.type)
397 {
398 case kEngineEventTypeNull:
399 break;
400
401 case kEngineEventTypeControl:
402 {
403 const EngineControlEvent& ctrlEvent = event.ctrl;
404
405 switch (ctrlEvent.type)
406 {
407 case kEngineControlEventTypeNull:
408 break;
409
410 case kEngineControlEventTypeParameter:
411 {
412 float value;
413
414 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
415 // Control backend stuff
416 if (event.channel == pData->ctrlChannel)
417 {
418 if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
419 {
420 value = ctrlEvent.normalizedValue;
421 setDryWetRT(value, true);
422 }
423
424 if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
425 {
426 value = ctrlEvent.normalizedValue*127.0f/100.0f;
427 setVolumeRT(value, true);
428 }
429
430 if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
431 {
432 float left, right;
433 value = ctrlEvent.normalizedValue/0.5f - 1.0f;
434
435 if (value < 0.0f)
436 {
437 left = -1.0f;
438 right = (value*2.0f)+1.0f;
439 }
440 else if (value > 0.0f)
441 {
442 left = (value*2.0f)-1.0f;
443 right = 1.0f;
444 }
445 else
446 {
447 left = -1.0f;
448 right = 1.0f;
449 }
450
451 setBalanceLeftRT(left, true);
452 setBalanceRightRT(right, true);
453 }
454 }
455 #endif
456 // Control plugin parameters
457 for (uint32_t k=0; k < pData->param.count; ++k)
458 {
459 if (pData->param.data[k].midiChannel != event.channel)
460 continue;
461 if (pData->param.data[k].mappedControlIndex != ctrlEvent.param)
462 continue;
463 if (pData->param.data[k].hints != PARAMETER_INPUT)
464 continue;
465 if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
466 continue;
467
468 value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
469 setParameterValueRT(k, value, true);
470 }
471
472 if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE)
473 {
474 fSynth.handleController(event.channel+1, ctrlEvent.param, int(ctrlEvent.normalizedValue*127.0f));
475 }
476
477 break;
478 }
479
480 case kEngineControlEventTypeMidiBank:
481 case kEngineControlEventTypeMidiProgram:
482 case kEngineControlEventTypeAllSoundOff:
483 break;
484
485 case kEngineControlEventTypeAllNotesOff:
486 if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
487 {
488 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
489 if (event.channel == pData->ctrlChannel && ! allNotesOffSent)
490 {
491 allNotesOffSent = true;
492 postponeRtAllNotesOff();
493 }
494 #endif
495
496 fSynth.allNotesOff(event.channel+1, true);
497 }
498 break;
499 }
500 break;
501 }
502
503 case kEngineEventTypeMidi: {
504 const EngineMidiEvent& midiEvent(event.midi);
505
506 const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data);
507
508 uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData));
509
510 if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES))
511 continue;
512 if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0)
513 continue;
514 if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0)
515 continue;
516 if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0)
517 continue;
518 if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0)
519 continue;
520
521 // Fix bad note-off
522 if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0)
523 status = MIDI_STATUS_NOTE_OFF;
524
525 // put back channel in data
526 uint8_t midiData2[midiEvent.size];
527 midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT));
528 std::memcpy(midiData2+1, midiData+1, static_cast<std::size_t>(midiEvent.size-1));
529
530 const MidiMessage midiMessage(midiData2, static_cast<int>(midiEvent.size), 0.0);
531
532 fSynth.handleMidiEvent(midiMessage);
533
534 if (status == MIDI_STATUS_NOTE_ON)
535 {
536 pData->postponeNoteOnRtEvent(true, event.channel, midiData[1], midiData[2]);
537 }
538 else if (status == MIDI_STATUS_NOTE_OFF)
539 {
540 pData->postponeNoteOffRtEvent(true, event.channel, midiData[1]);
541 }
542 } break;
543 }
544 }
545
546 pData->postRtEvents.trySplice();
547
548 if (frames > timeOffset)
549 processSingle(audioOutBuffer, frames - timeOffset, timeOffset);
550
551 } // End of Event Input and Processing
552
553 // --------------------------------------------------------------------------------------------------------
554 // Parameter outputs
555
556 fNumVoices = static_cast<float>(fSynth.numVoicesUsed());
557 }
558
processSingle(AudioSampleBuffer & audioOutBuffer,const uint32_t frames,const uint32_t timeOffset)559 bool processSingle(AudioSampleBuffer& audioOutBuffer, const uint32_t frames, const uint32_t timeOffset)
560 {
561 CARLA_SAFE_ASSERT_RETURN(frames > 0, false);
562
563 // --------------------------------------------------------------------------------------------------------
564 // Try lock, silence otherwise
565
566 #ifndef STOAT_TEST_BUILD
567 if (pData->engine->isOffline())
568 {
569 pData->singleMutex.lock();
570 }
571 else
572 #endif
573 if (! pData->singleMutex.tryLock())
574 {
575 audioOutBuffer.clear(timeOffset, frames);
576 return false;
577 }
578
579 // --------------------------------------------------------------------------------------------------------
580 // Run plugin
581
582 fSynth.renderVoices(audioOutBuffer, static_cast<int>(timeOffset), static_cast<int>(frames));
583
584 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
585 // --------------------------------------------------------------------------------------------------------
586 // Post-processing (dry/wet, volume and balance)
587
588 {
589 const bool doVolume = carla_isNotEqual(pData->postProc.volume, 1.0f);
590 //const bool doBalance = carla_isNotEqual(pData->postProc.balanceLeft, -1.0f) || carla_isNotEqual(pData->postProc.balanceRight, 1.0f);
591
592 float* outBufferL = audioOutBuffer.getWritePointer(0, timeOffset);
593 float* outBufferR = audioOutBuffer.getWritePointer(1, timeOffset);
594
595 #if 0
596 if (doBalance)
597 {
598 float oldBufLeft[frames];
599
600 // there was a loop here
601 {
602 if (i % 2 == 0)
603 carla_copyFloats(oldBufLeft, outBuffer[i], frames);
604
605 float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f;
606 float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f;
607
608 for (uint32_t k=0; k < frames; ++k)
609 {
610 if (i % 2 == 0)
611 {
612 // left
613 outBuffer[i][k] = oldBufLeft[k] * (1.0f - balRangeL);
614 outBuffer[i][k] += outBuffer[i+1][k] * (1.0f - balRangeR);
615 }
616 else
617 {
618 // right
619 outBuffer[i][k] = outBuffer[i][k] * balRangeR;
620 outBuffer[i][k] += oldBufLeft[k] * balRangeL;
621 }
622 }
623 }
624 }
625 #endif
626
627 if (doVolume)
628 {
629 const float volume = pData->postProc.volume;
630
631 for (uint32_t k=0; k < frames; ++k)
632 {
633 *outBufferL++ *= volume;
634 *outBufferR++ *= volume;
635 }
636 }
637
638 } // End of Post-processing
639 #endif
640
641 // --------------------------------------------------------------------------------------------------------
642
643 pData->singleMutex.unlock();
644 return true;
645 }
646
sampleRateChanged(const double newSampleRate)647 void sampleRateChanged(const double newSampleRate) override
648 {
649 fSynth.setCurrentPlaybackSampleRate(newSampleRate);
650 }
651
652 // -------------------------------------------------------------------
653 // Plugin buffers
654
655 // nothing
656
657 // -------------------------------------------------------------------
658
init(const CarlaPluginPtr plugin,const char * const filename,const char * const name,const char * const label,const uint options)659 bool init(const CarlaPluginPtr plugin,
660 const char* const filename, const char* const name, const char* const label, const uint options)
661 {
662 CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false);
663
664 // ---------------------------------------------------------------
665 // first checks
666
667 if (pData->client != nullptr)
668 {
669 pData->engine->setLastError("Plugin client is already registered");
670 return false;
671 }
672
673 if (filename == nullptr || filename[0] == '\0')
674 {
675 pData->engine->setLastError("null filename");
676 return false;
677 }
678
679 for (int i = 128; --i >=0;)
680 fSynth.addVoice(new sfzero::Voice());
681
682 // ---------------------------------------------------------------
683 // Init SFZero stuff
684
685 fSynth.setCurrentPlaybackSampleRate(pData->engine->getSampleRate());
686
687 File file(filename);
688 sfzero::Sound* const sound = new sfzero::Sound(file);
689
690 sfzero::Sound::LoadingIdleCallback cb = {
691 loadingIdleCallbackFunction,
692 pData->engine,
693 };
694
695 sound->loadRegions();
696 sound->loadSamples(cb);
697
698 if (fSynth.addSound(sound) == nullptr)
699 {
700 pData->engine->setLastError("Failed to allocate SFZ sounds in memory");
701 return false;
702 }
703
704 sound->dumpToConsole();
705
706 // ---------------------------------------------------------------
707
708 const String basename(File(filename).getFileNameWithoutExtension());
709
710 CarlaString label2(label != nullptr ? label : basename.toRawUTF8());
711
712 fLabel = label2.dup();
713 fRealName = carla_strdup(basename.toRawUTF8());
714
715 pData->filename = carla_strdup(filename);
716
717 if (name != nullptr && name[0] != '\0')
718 pData->name = pData->engine->getUniquePluginName(name);
719 else if (fRealName[0] != '\0')
720 pData->name = pData->engine->getUniquePluginName(fRealName);
721 else
722 pData->name = pData->engine->getUniquePluginName(fLabel);
723
724 // ---------------------------------------------------------------
725 // register client
726
727 pData->client = pData->engine->addClient(plugin);
728
729 if (pData->client == nullptr || ! pData->client->isOk())
730 {
731 pData->engine->setLastError("Failed to register plugin client");
732 return false;
733 }
734
735 // ---------------------------------------------------------------
736 // set options
737
738 pData->options = 0x0;
739
740 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES))
741 pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
742 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE))
743 pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
744 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH))
745 pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
746 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND))
747 pData->options |= PLUGIN_OPTION_SEND_PITCHBEND;
748 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF))
749 pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
750 if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES))
751 pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
752
753 return true;
754 }
755
756 // -------------------------------------------------------------------
757
758 private:
759 sfzero::Synth fSynth;
760 float fNumVoices;
761
762 const char* fLabel;
763 const char* fRealName;
764
765 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginSFZero)
766 };
767
768 // -------------------------------------------------------------------------------------------------------------------
769
newSFZero(const Initializer & init)770 CarlaPluginPtr CarlaPlugin::newSFZero(const Initializer& init)
771 {
772 carla_debug("CarlaPluginSFZero::newSFZero({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})",
773 init.engine, init.filename, init.name, init.label, init.uniqueId);
774
775 // -------------------------------------------------------------------
776 // Check if file exists
777
778 if (! File(init.filename).existsAsFile())
779 {
780 init.engine->setLastError("Requested file is not valid or does not exist");
781 return nullptr;
782 }
783
784 std::shared_ptr<CarlaPluginSFZero> plugin(new CarlaPluginSFZero(init.engine, init.id));
785
786 if (! plugin->init(plugin, init.filename, init.name, init.label, init.options))
787 return nullptr;
788
789 return plugin;
790 }
791
792 // -------------------------------------------------------------------------------------------------------------------
793
794 CARLA_BACKEND_END_NAMESPACE
795