3 * Copyright (C) 2018-2023 Filipe Coelho <falktx@falktx.com>
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.
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.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
18 #include "CarlaPluginInternal.hpp"
19 #include "CarlaEngine.hpp"
21 #ifndef STATIC_PLUGIN_TARGET
27 #include "CarlaBackendUtils.hpp"
29 #include "sfzero/SFZero.h"
31 #include "water/buffers/AudioSampleBuffer.h"
32 #include "water/files/File.h"
33 #include "water/midi/MidiMessage.h"
35 using water::AudioSampleBuffer
;
37 using water::MidiMessage
;
40 // -----------------------------------------------------------------------
42 CARLA_BACKEND_START_NAMESPACE
44 // -------------------------------------------------------------------------------------------------------------------
47 static const ExternalMidiNote kExternalMidiNoteFallback
= { -1, 0, 0 };
50 static void loadingIdleCallbackFunction(void* ptr
)
52 ((CarlaEngine
*)ptr
)->callback(true, false, ENGINE_CALLBACK_IDLE
, 0, 0, 0, 0, 0.0f
, nullptr);
55 // -------------------------------------------------------------------------------------------------------------------
57 class CarlaPluginSFZero
: public CarlaPlugin
60 CarlaPluginSFZero(CarlaEngine
* const engine
, const uint id
)
61 : CarlaPlugin(engine
, id
),
67 carla_debug("CarlaPluginSFZero::CarlaPluginSFZero(%p, %i)", engine
, id
);
70 ~CarlaPluginSFZero() override
72 carla_debug("CarlaPluginSFZero::~CarlaPluginSFZero()");
74 pData
->singleMutex
.lock();
75 pData
->masterMutex
.lock();
77 if (pData
->client
!= nullptr && pData
->client
->isActive())
78 pData
->client
->deactivate(true);
83 pData
->active
= false;
86 if (fLabel
!= nullptr)
92 if (fRealName
!= nullptr)
101 // -------------------------------------------------------------------
102 // Information (base)
104 PluginType
getType() const noexcept override
109 PluginCategory
getCategory() const noexcept override
111 return PLUGIN_CATEGORY_SYNTH
;
114 // -------------------------------------------------------------------
115 // Information (count)
119 // -------------------------------------------------------------------
120 // Information (current data)
124 // -------------------------------------------------------------------
125 // Information (per-plugin data)
127 uint
getOptionsAvailable() const noexcept override
131 options
|= PLUGIN_OPTION_SEND_CONTROL_CHANGES
;
132 options
|= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
;
133 options
|= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
;
134 options
|= PLUGIN_OPTION_SEND_PITCHBEND
;
135 options
|= PLUGIN_OPTION_SEND_ALL_SOUND_OFF
;
136 options
|= PLUGIN_OPTION_SKIP_SENDING_NOTES
;
141 float getParameterValue(const uint32_t parameterId
) const noexcept override
143 CARLA_SAFE_ASSERT_RETURN(parameterId
== 0, 0.0f
);
148 bool getLabel(char* const strBuf
) const noexcept override
150 if (fLabel
!= nullptr)
152 std::strncpy(strBuf
, fLabel
, STR_MAX
);
156 return CarlaPlugin::getLabel(strBuf
);
159 bool getMaker(char* const strBuf
) const noexcept override
161 std::strncpy(strBuf
, "SFZero engine", STR_MAX
);
165 bool getCopyright(char* const strBuf
) const noexcept override
167 std::strncpy(strBuf
, "ISC", STR_MAX
);
171 bool getRealName(char* const strBuf
) const noexcept override
173 if (fRealName
!= nullptr)
175 std::strncpy(strBuf
, fRealName
, STR_MAX
);
179 return CarlaPlugin::getRealName(strBuf
);
182 bool getParameterName(const uint32_t parameterId
, char* const strBuf
) const noexcept override
184 CARLA_SAFE_ASSERT_RETURN(parameterId
== 0, false);
186 std::strncpy(strBuf
, "Voice Count", STR_MAX
);
190 // -------------------------------------------------------------------
195 // -------------------------------------------------------------------
196 // Set data (internal stuff)
200 // -------------------------------------------------------------------
201 // Set data (plugin-specific stuff)
205 // -------------------------------------------------------------------
210 // -------------------------------------------------------------------
213 void reload() override
215 CARLA_SAFE_ASSERT_RETURN(pData
->engine
!= nullptr,);
216 carla_debug("CarlaPluginSFZero::reload() - start");
218 const EngineProcessMode
processMode(pData
->engine
->getProccessMode());
220 // Safely disable plugin for reload
221 const ScopedDisabler
sd(this);
228 pData
->audioOut
.createNew(2);
229 pData
->param
.createNew(1, false);
231 const uint
portNameSize(pData
->engine
->getMaxPortNameSize());
232 CarlaString portName
;
234 // ---------------------------------------
240 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
242 portName
= pData
->name
;
246 portName
+= "out-left";
247 portName
.truncate(portNameSize
);
249 pData
->audioOut
.ports
[0].port
= (CarlaEngineAudioPort
*)pData
->client
->addPort(kEnginePortTypeAudio
, portName
, false, 0);
250 pData
->audioOut
.ports
[0].rindex
= 0;
255 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
257 portName
= pData
->name
;
261 portName
+= "out-right";
262 portName
.truncate(portNameSize
);
264 pData
->audioOut
.ports
[1].port
= (CarlaEngineAudioPort
*)pData
->client
->addPort(kEnginePortTypeAudio
, portName
, false, 1);
265 pData
->audioOut
.ports
[1].rindex
= 1;
267 // ---------------------------------------
272 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
274 portName
= pData
->name
;
278 portName
+= "events-in";
279 portName
.truncate(portNameSize
);
281 pData
->event
.portIn
= (CarlaEngineEventPort
*)pData
->client
->addPort(kEnginePortTypeEvent
, portName
, true, 0);
283 // ---------------------------------------
286 pData
->param
.data
[0].type
= PARAMETER_OUTPUT
;
287 pData
->param
.data
[0].hints
= PARAMETER_IS_ENABLED
| PARAMETER_IS_AUTOMATABLE
| PARAMETER_IS_INTEGER
;
288 pData
->param
.data
[0].index
= 0;
289 pData
->param
.data
[0].rindex
= 0;
290 pData
->param
.ranges
[0].min
= 0.0f
;
291 pData
->param
.ranges
[0].max
= 128;
292 pData
->param
.ranges
[0].def
= 0.0f
;
293 pData
->param
.ranges
[0].step
= 1.0f
;
294 pData
->param
.ranges
[0].stepSmall
= 1.0f
;
295 pData
->param
.ranges
[0].stepLarge
= 1.0f
;
297 // ---------------------------------------
301 pData
->hints
|= PLUGIN_IS_SYNTH
;
302 pData
->hints
|= PLUGIN_CAN_VOLUME
;
303 pData
->hints
|= PLUGIN_CAN_BALANCE
;
305 // extra plugin hints
306 pData
->extraHints
= 0x0;
307 pData
->extraHints
|= PLUGIN_EXTRA_HINT_HAS_MIDI_IN
;
309 bufferSizeChanged(pData
->engine
->getBufferSize());
310 reloadPrograms(true);
315 carla_debug("CarlaPluginSFZero::reload() - end");
318 // -------------------------------------------------------------------
321 void process(const float* const* const, float** const audioOut
, const float* const*, float**, const uint32_t frames
) override
323 // --------------------------------------------------------------------------------------------------------
328 // disable any output sound
329 for (uint32_t i
=0; i
< pData
->audioOut
.count
; ++i
)
330 carla_zeroFloats(audioOut
[i
], frames
);
336 // --------------------------------------------------------------------------------------------------------
337 // Check if needs reset
339 if (pData
->needsReset
)
341 fSynth
.allNotesOff(0, false);
342 pData
->needsReset
= false;
345 // --------------------------------------------------------------------------------------------------------
346 // Event Input and Processing
349 // ----------------------------------------------------------------------------------------------------
350 // Setup audio buffer
352 AudioSampleBuffer
audioOutBuffer(audioOut
, 2, frames
);
354 // ----------------------------------------------------------------------------------------------------
355 // MIDI Input (External)
357 if (pData
->extNotes
.mutex
.tryLock())
359 for (RtLinkedList
<ExternalMidiNote
>::Itenerator it
= pData
->extNotes
.data
.begin2(); it
.valid(); it
.next())
361 const ExternalMidiNote
& note(it
.getValue(kExternalMidiNoteFallback
));
362 CARLA_SAFE_ASSERT_CONTINUE(note
.channel
>= 0 && note
.channel
< MAX_MIDI_CHANNELS
);
365 fSynth
.noteOn(note
.channel
+1, note
.note
, static_cast<float>(note
.velo
)/127.0f
);
367 fSynth
.noteOff(note
.channel
+1, note
.note
, static_cast<float>(note
.velo
)/127.0f
, true);
370 pData
->extNotes
.data
.clear();
371 pData
->extNotes
.mutex
.unlock();
373 } // End of MIDI Input (External)
375 // ----------------------------------------------------------------------------------------------------
376 // Event Input (System)
378 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
379 bool allNotesOffSent
= false;
381 uint32_t timeOffset
= 0;
383 for (uint32_t i
=0, numEvents
=pData
->event
.portIn
->getEventCount(); i
< numEvents
; ++i
)
385 const EngineEvent
& event(pData
->event
.portIn
->getEvent(i
));
387 uint32_t eventTime
= event
.time
;
388 CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime
< frames
, eventTime
, frames
);
390 if (eventTime
< timeOffset
)
392 carla_stderr2("Timing error, eventTime:%u < timeOffset:%u for '%s'",
393 eventTime
, timeOffset
, pData
->name
);
394 eventTime
= timeOffset
;
396 else if (eventTime
> timeOffset
)
398 if (processSingle(audioOutBuffer
, eventTime
- timeOffset
, timeOffset
))
399 timeOffset
= eventTime
;
405 case kEngineEventTypeNull
:
408 case kEngineEventTypeControl
:
410 const EngineControlEvent
& ctrlEvent
= event
.ctrl
;
412 switch (ctrlEvent
.type
)
414 case kEngineControlEventTypeNull
:
417 case kEngineControlEventTypeParameter
:
421 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
422 // Control backend stuff
423 if (event
.channel
== pData
->ctrlChannel
)
425 if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent
.param
) && (pData
->hints
& PLUGIN_CAN_DRYWET
) != 0)
427 value
= ctrlEvent
.normalizedValue
;
428 setDryWetRT(value
, true);
431 if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent
.param
) && (pData
->hints
& PLUGIN_CAN_VOLUME
) != 0)
433 value
= ctrlEvent
.normalizedValue
*127.0f
/100.0f
;
434 setVolumeRT(value
, true);
437 if (MIDI_IS_CONTROL_BALANCE(ctrlEvent
.param
) && (pData
->hints
& PLUGIN_CAN_BALANCE
) != 0)
440 value
= ctrlEvent
.normalizedValue
/0.5f
- 1.0f
;
445 right
= (value
*2.0f
)+1.0f
;
447 else if (value
> 0.0f
)
449 left
= (value
*2.0f
)-1.0f
;
458 setBalanceLeftRT(left
, true);
459 setBalanceRightRT(right
, true);
463 // Control plugin parameters
464 for (uint32_t k
=0; k
< pData
->param
.count
; ++k
)
466 if (pData
->param
.data
[k
].midiChannel
!= event
.channel
)
468 if (pData
->param
.data
[k
].mappedControlIndex
!= ctrlEvent
.param
)
470 if (pData
->param
.data
[k
].hints
!= PARAMETER_INPUT
)
472 if ((pData
->param
.data
[k
].hints
& PARAMETER_IS_AUTOMATABLE
) == 0)
475 value
= pData
->param
.getFinalUnnormalizedValue(k
, ctrlEvent
.normalizedValue
);
476 setParameterValueRT(k
, value
, eventTime
, true);
479 if ((pData
->options
& PLUGIN_OPTION_SEND_CONTROL_CHANGES
) != 0 && ctrlEvent
.param
< MAX_MIDI_VALUE
)
481 fSynth
.handleController(event
.channel
+1, ctrlEvent
.param
, int(ctrlEvent
.normalizedValue
*127.0f
+ 0.5f
));
487 case kEngineControlEventTypeMidiBank
:
488 case kEngineControlEventTypeMidiProgram
:
489 case kEngineControlEventTypeAllSoundOff
:
492 case kEngineControlEventTypeAllNotesOff
:
493 if (pData
->options
& PLUGIN_OPTION_SEND_ALL_SOUND_OFF
)
495 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
496 if (event
.channel
== pData
->ctrlChannel
&& ! allNotesOffSent
)
498 allNotesOffSent
= true;
499 postponeRtAllNotesOff();
503 fSynth
.allNotesOff(event
.channel
+1, true);
510 case kEngineEventTypeMidi
: {
511 const EngineMidiEvent
& midiEvent(event
.midi
);
513 if (midiEvent
.size
> EngineMidiEvent::kDataSize
)
516 uint8_t status
= uint8_t(MIDI_GET_STATUS_FROM_DATA(midiEvent
.data
));
518 if ((status
== MIDI_STATUS_NOTE_OFF
|| status
== MIDI_STATUS_NOTE_ON
) && (pData
->options
& PLUGIN_OPTION_SKIP_SENDING_NOTES
))
520 if (status
== MIDI_STATUS_CHANNEL_PRESSURE
&& (pData
->options
& PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
) == 0)
522 if (status
== MIDI_STATUS_CONTROL_CHANGE
&& (pData
->options
& PLUGIN_OPTION_SEND_CONTROL_CHANGES
) == 0)
524 if (status
== MIDI_STATUS_POLYPHONIC_AFTERTOUCH
&& (pData
->options
& PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
) == 0)
526 if (status
== MIDI_STATUS_PITCH_WHEEL_CONTROL
&& (pData
->options
& PLUGIN_OPTION_SEND_PITCHBEND
) == 0)
530 if (status
== MIDI_STATUS_NOTE_ON
&& midiEvent
.data
[2] == 0)
531 status
= MIDI_STATUS_NOTE_OFF
;
533 // put back channel in data
534 uint8_t midiData2
[EngineMidiEvent::kDataSize
];
535 midiData2
[0] = uint8_t(status
| (event
.channel
& MIDI_CHANNEL_BIT
));
536 std::memcpy(midiData2
+ 1, midiEvent
.data
+ 1, static_cast<std::size_t>(midiEvent
.size
- 1));
538 const MidiMessage
midiMessage(midiData2
, static_cast<int>(midiEvent
.size
), 0.0);
540 fSynth
.handleMidiEvent(midiMessage
);
542 if (status
== MIDI_STATUS_NOTE_ON
)
544 pData
->postponeNoteOnRtEvent(true, event
.channel
, midiEvent
.data
[1], midiEvent
.data
[2]);
546 else if (status
== MIDI_STATUS_NOTE_OFF
)
548 pData
->postponeNoteOffRtEvent(true, event
.channel
, midiEvent
.data
[1]);
554 pData
->postRtEvents
.trySplice();
556 if (frames
> timeOffset
)
557 processSingle(audioOutBuffer
, frames
- timeOffset
, timeOffset
);
559 } // End of Event Input and Processing
561 // --------------------------------------------------------------------------------------------------------
564 fNumVoices
= static_cast<float>(fSynth
.numVoicesUsed());
567 bool processSingle(AudioSampleBuffer
& audioOutBuffer
, const uint32_t frames
, const uint32_t timeOffset
)
569 CARLA_SAFE_ASSERT_RETURN(frames
> 0, false);
571 // --------------------------------------------------------------------------------------------------------
572 // Try lock, silence otherwise
574 #ifndef STOAT_TEST_BUILD
575 if (pData
->engine
->isOffline())
577 pData
->singleMutex
.lock();
581 if (! pData
->singleMutex
.tryLock())
583 audioOutBuffer
.clear(timeOffset
, frames
);
587 // --------------------------------------------------------------------------------------------------------
590 fSynth
.renderVoices(audioOutBuffer
, static_cast<int>(timeOffset
), static_cast<int>(frames
));
592 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
593 // --------------------------------------------------------------------------------------------------------
594 // Post-processing (dry/wet, volume and balance)
597 const bool doVolume
= carla_isNotEqual(pData
->postProc
.volume
, 1.0f
);
598 //const bool doBalance = carla_isNotEqual(pData->postProc.balanceLeft, -1.0f) || carla_isNotEqual(pData->postProc.balanceRight, 1.0f);
600 float* outBufferL
= audioOutBuffer
.getWritePointer(0, timeOffset
);
601 float* outBufferR
= audioOutBuffer
.getWritePointer(1, timeOffset
);
606 float* const oldBufLeft
= pData
->postProc
.extraBuffer
;
608 // there was a loop here
611 carla_copyFloats(oldBufLeft
, outBuffer
[i
], frames
);
613 float balRangeL
= (pData
->postProc
.balanceLeft
+ 1.0f
)/2.0f
;
614 float balRangeR
= (pData
->postProc
.balanceRight
+ 1.0f
)/2.0f
;
616 for (uint32_t k
=0; k
< frames
; ++k
)
621 outBuffer
[i
][k
] = oldBufLeft
[k
] * (1.0f
- balRangeL
);
622 outBuffer
[i
][k
] += outBuffer
[i
+1][k
] * (1.0f
- balRangeR
);
627 outBuffer
[i
][k
] = outBuffer
[i
][k
] * balRangeR
;
628 outBuffer
[i
][k
] += oldBufLeft
[k
] * balRangeL
;
637 const float volume
= pData
->postProc
.volume
;
639 for (uint32_t k
=0; k
< frames
; ++k
)
641 *outBufferL
++ *= volume
;
642 *outBufferR
++ *= volume
;
646 } // End of Post-processing
649 // --------------------------------------------------------------------------------------------------------
651 pData
->singleMutex
.unlock();
655 void sampleRateChanged(const double newSampleRate
) override
657 fSynth
.setCurrentPlaybackSampleRate(newSampleRate
);
660 // -------------------------------------------------------------------
665 // -------------------------------------------------------------------
667 bool init(const CarlaPluginPtr plugin
,
668 const char* const filename
, const char* const name
, const char* const label
, const uint options
)
670 CARLA_SAFE_ASSERT_RETURN(pData
->engine
!= nullptr, false);
672 // ---------------------------------------------------------------
675 if (pData
->client
!= nullptr)
677 pData
->engine
->setLastError("Plugin client is already registered");
681 if (filename
== nullptr || filename
[0] == '\0')
683 pData
->engine
->setLastError("null filename");
687 for (int i
= 128; --i
>=0;)
688 fSynth
.addVoice(new sfzero::Voice());
690 // ---------------------------------------------------------------
693 fSynth
.setCurrentPlaybackSampleRate(pData
->engine
->getSampleRate());
696 sfzero::Sound
* const sound
= new sfzero::Sound(file
);
698 sfzero::Sound::LoadingIdleCallback cb
= {
699 loadingIdleCallbackFunction
,
703 sound
->loadRegions();
704 sound
->loadSamples(cb
);
706 if (fSynth
.addSound(sound
) == nullptr)
708 pData
->engine
->setLastError("Failed to allocate SFZ sounds in memory");
712 sound
->dumpToConsole();
714 // ---------------------------------------------------------------
716 const String
basename(File(filename
).getFileNameWithoutExtension());
718 CarlaString
label2(label
!= nullptr ? label
: basename
.toRawUTF8());
720 fLabel
= label2
.dup();
721 fRealName
= carla_strdup(basename
.toRawUTF8());
723 pData
->filename
= carla_strdup(filename
);
725 if (name
!= nullptr && name
[0] != '\0')
726 pData
->name
= pData
->engine
->getUniquePluginName(name
);
727 else if (fRealName
[0] != '\0')
728 pData
->name
= pData
->engine
->getUniquePluginName(fRealName
);
730 pData
->name
= pData
->engine
->getUniquePluginName(fLabel
);
732 // ---------------------------------------------------------------
735 pData
->client
= pData
->engine
->addClient(plugin
);
737 if (pData
->client
== nullptr || ! pData
->client
->isOk())
739 pData
->engine
->setLastError("Failed to register plugin client");
743 // ---------------------------------------------------------------
746 pData
->options
= 0x0;
748 if (isPluginOptionEnabled(options
, PLUGIN_OPTION_SEND_CONTROL_CHANGES
))
749 pData
->options
|= PLUGIN_OPTION_SEND_CONTROL_CHANGES
;
750 if (isPluginOptionEnabled(options
, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
))
751 pData
->options
|= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
;
752 if (isPluginOptionEnabled(options
, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
))
753 pData
->options
|= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
;
754 if (isPluginOptionEnabled(options
, PLUGIN_OPTION_SEND_PITCHBEND
))
755 pData
->options
|= PLUGIN_OPTION_SEND_PITCHBEND
;
756 if (isPluginOptionEnabled(options
, PLUGIN_OPTION_SEND_ALL_SOUND_OFF
))
757 pData
->options
|= PLUGIN_OPTION_SEND_ALL_SOUND_OFF
;
758 if (isPluginOptionInverseEnabled(options
, PLUGIN_OPTION_SKIP_SENDING_NOTES
))
759 pData
->options
|= PLUGIN_OPTION_SKIP_SENDING_NOTES
;
764 // -------------------------------------------------------------------
767 sfzero::Synth fSynth
;
771 const char* fRealName
;
773 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginSFZero
)
776 CARLA_BACKEND_END_NAMESPACE
780 CARLA_BACKEND_START_NAMESPACE
782 // -------------------------------------------------------------------------------------------------------------------
784 CarlaPluginPtr
CarlaPlugin::newSFZero(const Initializer
& init
)
786 carla_debug("CarlaPluginSFZero::newSFZero({%p, \"%s\", \"%s\", \"%s\", " P_INT64
"})",
787 init
.engine
, init
.filename
, init
.name
, init
.label
, init
.uniqueId
);
790 // -------------------------------------------------------------------
791 // Check if file exists
793 if (! water::File(init
.filename
).existsAsFile())
795 init
.engine
->setLastError("Requested file is not valid or does not exist");
799 std::shared_ptr
<CarlaPluginSFZero
> plugin(new CarlaPluginSFZero(init
.engine
, init
.id
));
801 if (! plugin
->init(plugin
, init
.filename
, init
.name
, init
.label
, init
.options
))
806 init
.engine
->setLastError("SFZ support not available");
811 // -------------------------------------------------------------------------------------------------------------------
813 CARLA_BACKEND_END_NAMESPACE