| @@ -0,0 +1,801 @@ | |||
| /* | |||
| * Carla SFZero Plugin | |||
| * Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #include "CarlaPluginInternal.hpp" | |||
| #include "CarlaEngine.hpp" | |||
| #include "sfzero/SFZero.h" | |||
| #include "water/audioformat/AudioFormatManager.h" | |||
| #include "water/buffers/AudioSampleBuffer.h" | |||
| #include "water/files/File.h" | |||
| #include "water/memory/SharedResourcePointer.h" | |||
| #include "water/midi/MidiMessage.h" | |||
| using water::AudioFormatManager; | |||
| using water::AudioSampleBuffer; | |||
| using water::File; | |||
| using water::MidiMessage; | |||
| using water::SharedResourcePointer; | |||
| using water::String; | |||
| // ----------------------------------------------------------------------- | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| // Fallback data | |||
| static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 }; | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| class AutoAudioFormatManager : public AudioFormatManager | |||
| { | |||
| public: | |||
| AutoAudioFormatManager() | |||
| : AudioFormatManager() | |||
| { | |||
| registerBasicFormats(); | |||
| } | |||
| }; | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| class CarlaPluginSFZero : public CarlaPlugin | |||
| { | |||
| public: | |||
| CarlaPluginSFZero(CarlaEngine* const engine, const uint id) | |||
| : CarlaPlugin(engine, id), | |||
| fSynth(), | |||
| fNumVoices(0.0f), | |||
| fLabel(nullptr), | |||
| fRealName(nullptr) | |||
| { | |||
| carla_debug("CarlaPluginSFZero::CarlaPluginSFZero(%p, %i)", engine, id); | |||
| } | |||
| ~CarlaPluginSFZero() override | |||
| { | |||
| carla_debug("CarlaPluginSFZero::~CarlaPluginSFZero()"); | |||
| pData->singleMutex.lock(); | |||
| pData->masterMutex.lock(); | |||
| if (pData->client != nullptr && pData->client->isActive()) | |||
| pData->client->deactivate(); | |||
| if (pData->active) | |||
| { | |||
| deactivate(); | |||
| pData->active = false; | |||
| } | |||
| if (fLabel != nullptr) | |||
| { | |||
| delete[] fLabel; | |||
| fLabel = nullptr; | |||
| } | |||
| if (fRealName != nullptr) | |||
| { | |||
| delete[] fRealName; | |||
| fRealName = nullptr; | |||
| } | |||
| clearBuffers(); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Information (base) | |||
| PluginType getType() const noexcept override | |||
| { | |||
| return PLUGIN_SFZ; | |||
| } | |||
| PluginCategory getCategory() const noexcept override | |||
| { | |||
| return PLUGIN_CATEGORY_SYNTH; | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Information (count) | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Information (current data) | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Information (per-plugin data) | |||
| uint getOptionsAvailable() const noexcept override | |||
| { | |||
| uint options = 0x0; | |||
| options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; | |||
| options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; | |||
| options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; | |||
| options |= PLUGIN_OPTION_SEND_PITCHBEND; | |||
| options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; | |||
| return options; | |||
| } | |||
| float getParameterValue(const uint32_t parameterId) const noexcept override | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(parameterId == 0, 0.0f); | |||
| return fNumVoices; | |||
| } | |||
| void getLabel(char* const strBuf) const noexcept override | |||
| { | |||
| if (fLabel != nullptr) | |||
| { | |||
| std::strncpy(strBuf, fLabel, STR_MAX); | |||
| return; | |||
| } | |||
| CarlaPlugin::getLabel(strBuf); | |||
| } | |||
| void getMaker(char* const strBuf) const noexcept override | |||
| { | |||
| std::strncpy(strBuf, "SFZero engine", STR_MAX); | |||
| } | |||
| void getCopyright(char* const strBuf) const noexcept override | |||
| { | |||
| std::strncpy(strBuf, "ISC", STR_MAX); | |||
| } | |||
| void getRealName(char* const strBuf) const noexcept override | |||
| { | |||
| if (fRealName != nullptr) | |||
| { | |||
| std::strncpy(strBuf, fRealName, STR_MAX); | |||
| return; | |||
| } | |||
| CarlaPlugin::getRealName(strBuf); | |||
| } | |||
| void getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(parameterId == 0,); | |||
| std::strncpy(strBuf, "Voice Count", STR_MAX); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Set data (state) | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Set data (internal stuff) | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Set data (plugin-specific stuff) | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Set ui stuff | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| // Plugin state | |||
| void reload() override | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); | |||
| carla_debug("CarlaPluginSFZero::reload() - start"); | |||
| const EngineProcessMode processMode(pData->engine->getProccessMode()); | |||
| // Safely disable plugin for reload | |||
| const ScopedDisabler sd(this); | |||
| if (pData->active) | |||
| deactivate(); | |||
| clearBuffers(); | |||
| pData->audioOut.createNew(2); | |||
| pData->param.createNew(1, false); | |||
| const uint portNameSize(pData->engine->getMaxPortNameSize()); | |||
| CarlaString portName; | |||
| // --------------------------------------- | |||
| // Audio Outputs | |||
| // out-left | |||
| portName.clear(); | |||
| if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) | |||
| { | |||
| portName = pData->name; | |||
| portName += ":"; | |||
| } | |||
| portName += "out-left"; | |||
| portName.truncate(portNameSize); | |||
| pData->audioOut.ports[0].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 0); | |||
| pData->audioOut.ports[0].rindex = 0; | |||
| // out-right | |||
| portName.clear(); | |||
| if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) | |||
| { | |||
| portName = pData->name; | |||
| portName += ":"; | |||
| } | |||
| portName += "out-right"; | |||
| portName.truncate(portNameSize); | |||
| pData->audioOut.ports[1].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 1); | |||
| pData->audioOut.ports[1].rindex = 1; | |||
| // --------------------------------------- | |||
| // Event Input | |||
| portName.clear(); | |||
| if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) | |||
| { | |||
| portName = pData->name; | |||
| portName += ":"; | |||
| } | |||
| portName += "events-in"; | |||
| portName.truncate(portNameSize); | |||
| pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); | |||
| // --------------------------------------- | |||
| // Parameters | |||
| pData->param.data[0].type = PARAMETER_OUTPUT; | |||
| pData->param.data[0].hints = PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE | PARAMETER_IS_INTEGER; | |||
| pData->param.data[0].index = 0; | |||
| pData->param.data[0].rindex = 0; | |||
| pData->param.ranges[0].min = 0.0f; | |||
| pData->param.ranges[0].max = 128; | |||
| pData->param.ranges[0].def = 0.0f; | |||
| pData->param.ranges[0].step = 1.0f; | |||
| pData->param.ranges[0].stepSmall = 1.0f; | |||
| pData->param.ranges[0].stepLarge = 1.0f; | |||
| // --------------------------------------- | |||
| // plugin hints | |||
| pData->hints = 0x0; | |||
| pData->hints |= PLUGIN_IS_SYNTH; | |||
| pData->hints |= PLUGIN_CAN_VOLUME; | |||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||
| // extra plugin hints | |||
| pData->extraHints = 0x0; | |||
| pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN; | |||
| pData->extraHints |= PLUGIN_EXTRA_HINT_CAN_RUN_RACK; | |||
| bufferSizeChanged(pData->engine->getBufferSize()); | |||
| reloadPrograms(true); | |||
| if (pData->active) | |||
| activate(); | |||
| carla_debug("CarlaPluginSFZero::reload() - end"); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Plugin processing | |||
| void process(const float** const, float** const audioOut, const float** const, float** const, const uint32_t frames) override | |||
| { | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Check if active | |||
| if (! pData->active) | |||
| { | |||
| // disable any output sound | |||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||
| carla_zeroFloats(audioOut[i], frames); | |||
| fNumVoices = 0.0f; | |||
| return; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Check if needs reset | |||
| if (pData->needsReset) | |||
| { | |||
| fSynth.allNotesOff(0, false); | |||
| pData->needsReset = false; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Event Input and Processing | |||
| { | |||
| // ---------------------------------------------------------------------------------------------------- | |||
| // Setup audio buffer | |||
| AudioSampleBuffer audioOutBuffer(audioOut, 2, frames); | |||
| // ---------------------------------------------------------------------------------------------------- | |||
| // MIDI Input (External) | |||
| if (pData->extNotes.mutex.tryLock()) | |||
| { | |||
| for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next()) | |||
| { | |||
| const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback)); | |||
| CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS); | |||
| if (note.velo > 0) | |||
| fSynth.noteOn(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f); | |||
| else | |||
| fSynth.noteOff(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f, true); | |||
| } | |||
| pData->extNotes.data.clear(); | |||
| pData->extNotes.mutex.unlock(); | |||
| } // End of MIDI Input (External) | |||
| // ---------------------------------------------------------------------------------------------------- | |||
| // Event Input (System) | |||
| #ifndef BUILD_BRIDGE | |||
| bool allNotesOffSent = false; | |||
| #endif | |||
| uint32_t timeOffset = 0; | |||
| for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i) | |||
| { | |||
| const EngineEvent& event(pData->event.portIn->getEvent(i)); | |||
| uint32_t eventTime = event.time; | |||
| CARLA_SAFE_ASSERT_CONTINUE(eventTime < frames); | |||
| if (eventTime < timeOffset) | |||
| { | |||
| carla_stderr2("Timing error, eventTime:%u < timeOffset:%u for '%s'", | |||
| eventTime, timeOffset, pData->name); | |||
| eventTime = timeOffset; | |||
| } | |||
| else if (eventTime > timeOffset) | |||
| { | |||
| if (processSingle(audioOutBuffer, eventTime - timeOffset, timeOffset)) | |||
| timeOffset = eventTime; | |||
| } | |||
| // Control change | |||
| switch (event.type) | |||
| { | |||
| case kEngineEventTypeNull: | |||
| break; | |||
| case kEngineEventTypeControl: | |||
| { | |||
| const EngineControlEvent& ctrlEvent = event.ctrl; | |||
| switch (ctrlEvent.type) | |||
| { | |||
| case kEngineControlEventTypeNull: | |||
| break; | |||
| case kEngineControlEventTypeParameter: | |||
| { | |||
| #ifndef BUILD_BRIDGE | |||
| // Control backend stuff | |||
| if (event.channel == pData->ctrlChannel) | |||
| { | |||
| float value; | |||
| if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) | |||
| { | |||
| value = ctrlEvent.value; | |||
| setDryWetRT(value); | |||
| } | |||
| if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0) | |||
| { | |||
| value = ctrlEvent.value*127.0f/100.0f; | |||
| setVolumeRT(value); | |||
| } | |||
| if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0) | |||
| { | |||
| float left, right; | |||
| value = ctrlEvent.value/0.5f - 1.0f; | |||
| if (value < 0.0f) | |||
| { | |||
| left = -1.0f; | |||
| right = (value*2.0f)+1.0f; | |||
| } | |||
| else if (value > 0.0f) | |||
| { | |||
| left = (value*2.0f)-1.0f; | |||
| right = 1.0f; | |||
| } | |||
| else | |||
| { | |||
| left = -1.0f; | |||
| right = 1.0f; | |||
| } | |||
| setBalanceLeftRT(left); | |||
| setBalanceRightRT(right); | |||
| } | |||
| } | |||
| #endif | |||
| // Control plugin parameters | |||
| for (uint32_t k=0; k < pData->param.count; ++k) | |||
| { | |||
| if (pData->param.data[k].midiChannel != event.channel) | |||
| continue; | |||
| if (pData->param.data[k].midiCC != ctrlEvent.param) | |||
| continue; | |||
| if (pData->param.data[k].hints != PARAMETER_INPUT) | |||
| continue; | |||
| if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0) | |||
| continue; | |||
| float value; | |||
| if (pData->param.data[k].hints & PARAMETER_IS_BOOLEAN) | |||
| { | |||
| value = (ctrlEvent.value < 0.5f) ? pData->param.ranges[k].min : pData->param.ranges[k].max; | |||
| } | |||
| else | |||
| { | |||
| value = pData->param.ranges[k].getUnnormalizedValue(ctrlEvent.value); | |||
| if (pData->param.data[k].hints & PARAMETER_IS_INTEGER) | |||
| value = std::rint(value); | |||
| } | |||
| setParameterValueRT(k, value); | |||
| } | |||
| if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_CONTROL) | |||
| { | |||
| fSynth.handleController(event.channel+1, ctrlEvent.param, int(ctrlEvent.value*127.0f)); | |||
| } | |||
| break; | |||
| } | |||
| case kEngineControlEventTypeMidiBank: | |||
| case kEngineControlEventTypeMidiProgram: | |||
| case kEngineControlEventTypeAllSoundOff: | |||
| break; | |||
| case kEngineControlEventTypeAllNotesOff: | |||
| if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) | |||
| { | |||
| #ifndef BUILD_BRIDGE | |||
| if (event.channel == pData->ctrlChannel && ! allNotesOffSent) | |||
| { | |||
| allNotesOffSent = true; | |||
| sendMidiAllNotesOffToCallback(); | |||
| } | |||
| #endif | |||
| fSynth.allNotesOff(event.channel+1, true); | |||
| } | |||
| break; | |||
| } | |||
| break; | |||
| } | |||
| case kEngineEventTypeMidi: { | |||
| const EngineMidiEvent& midiEvent(event.midi); | |||
| const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data); | |||
| uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData)); | |||
| if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) | |||
| continue; | |||
| if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) | |||
| continue; | |||
| if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) | |||
| continue; | |||
| if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) | |||
| continue; | |||
| // Fix bad note-off | |||
| if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0) | |||
| status = MIDI_STATUS_NOTE_OFF; | |||
| // put back channel in data | |||
| uint8_t midiData2[midiEvent.size]; | |||
| midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT)); | |||
| std::memcpy(midiData2+1, midiData+1, static_cast<std::size_t>(midiEvent.size-1)); | |||
| const MidiMessage midiMessage(midiData2, static_cast<int>(midiEvent.size), 0.0); | |||
| fSynth.handleMidiEvent(midiMessage); | |||
| if (status == MIDI_STATUS_NOTE_ON) | |||
| pData->postponeRtEvent(kPluginPostRtEventNoteOn, event.channel, midiData[1], midiData[2]); | |||
| else if (status == MIDI_STATUS_NOTE_OFF) | |||
| pData->postponeRtEvent(kPluginPostRtEventNoteOff, event.channel, midiData[1], 0.0f); | |||
| } break; | |||
| } | |||
| } | |||
| pData->postRtEvents.trySplice(); | |||
| if (frames > timeOffset) | |||
| processSingle(audioOutBuffer, frames - timeOffset, timeOffset); | |||
| } // End of Event Input and Processing | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Parameter outputs | |||
| fNumVoices = fSynth.numVoicesUsed(); | |||
| } | |||
| bool processSingle(AudioSampleBuffer& audioOutBuffer, const uint32_t frames, const uint32_t timeOffset) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(frames > 0, false); | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Try lock, silence otherwise | |||
| #ifndef STOAT_TEST_BUILD | |||
| if (pData->engine->isOffline()) | |||
| { | |||
| pData->singleMutex.lock(); | |||
| } | |||
| else | |||
| #endif | |||
| if (! pData->singleMutex.tryLock()) | |||
| { | |||
| audioOutBuffer.clear(timeOffset, frames); | |||
| return false; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Run plugin | |||
| fSynth.renderVoices(audioOutBuffer, timeOffset, frames); | |||
| #if 0 // ndef BUILD_BRIDGE | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Post-processing (dry/wet, volume and balance) | |||
| { | |||
| const bool doVolume = carla_isNotEqual(pData->postProc.volume, 1.0f); | |||
| // const bool doBalance = carla_isNotEqual(pData->postProc.balanceLeft, -1.0f) || carla_isNotEqual(pData->postProc.balanceRight, 1.0f); | |||
| float* outBufferL = audioOutBuffer.getWritePointer(0, timeOffset); | |||
| float* outBufferR = audioOutBuffer.getWritePointer(1, timeOffset); | |||
| #if 0 | |||
| if (doBalance) | |||
| { | |||
| float oldBufLeft[frames]; | |||
| // there was a loop here | |||
| { | |||
| if (i % 2 == 0) | |||
| carla_copyFloats(oldBufLeft, outBuffer[i], frames); | |||
| float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f; | |||
| float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f; | |||
| for (uint32_t k=0; k < frames; ++k) | |||
| { | |||
| if (i % 2 == 0) | |||
| { | |||
| // left | |||
| outBuffer[i][k] = oldBufLeft[k] * (1.0f - balRangeL); | |||
| outBuffer[i][k] += outBuffer[i+1][k] * (1.0f - balRangeR); | |||
| } | |||
| else | |||
| { | |||
| // right | |||
| outBuffer[i][k] = outBuffer[i][k] * balRangeR; | |||
| outBuffer[i][k] += oldBufLeft[k] * balRangeL; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| #endif | |||
| if (doVolume) | |||
| { | |||
| for (uint32_t k=0; k < frames; ++k) | |||
| { | |||
| *outBufferL++ *= pData->postProc.volume; | |||
| *outBufferR++ *= pData->postProc.volume; | |||
| } | |||
| } | |||
| } // End of Post-processing | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| pData->singleMutex.unlock(); | |||
| return true; | |||
| } | |||
| void sampleRateChanged(const double newSampleRate) override | |||
| { | |||
| fSynth.setCurrentPlaybackSampleRate(newSampleRate); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Plugin buffers | |||
| // nothing | |||
| // ------------------------------------------------------------------- | |||
| bool init(const char* const filename, const char* const name, const char* const label, const uint options) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); | |||
| // --------------------------------------------------------------- | |||
| // first checks | |||
| if (pData->client != nullptr) | |||
| { | |||
| pData->engine->setLastError("Plugin client is already registered"); | |||
| return false; | |||
| } | |||
| if (filename == nullptr || filename[0] == '\0') | |||
| { | |||
| pData->engine->setLastError("null filename"); | |||
| return false; | |||
| } | |||
| for (int i = 128; --i >=0;) | |||
| fSynth.addVoice(new sfzero::Voice()); | |||
| // --------------------------------------------------------------- | |||
| // Init SFZero stuff | |||
| fSynth.setCurrentPlaybackSampleRate(pData->engine->getSampleRate()); | |||
| File file(filename); | |||
| sfzero::Sound* const sound = new sfzero::Sound(file); | |||
| sound->loadRegions(); | |||
| sound->loadSamples(sAudioFormatManager.getPointer(), nullptr, nullptr); | |||
| if (fSynth.addSound(sound) == nullptr) | |||
| { | |||
| pData->engine->setLastError("Failed to allocate SFZ sounds in memory"); | |||
| return false; | |||
| } | |||
| // --------------------------------------------------------------- | |||
| const String basename(File(filename).getFileNameWithoutExtension()); | |||
| CarlaString label2(label != nullptr ? label : basename.toRawUTF8()); | |||
| fLabel = label2.dup(); | |||
| fRealName = carla_strdup(basename.toRawUTF8()); | |||
| pData->filename = carla_strdup(filename); | |||
| if (name != nullptr && name[0] != '\0') | |||
| pData->name = pData->engine->getUniquePluginName(name); | |||
| else if (fRealName[0] != '\0') | |||
| pData->name = pData->engine->getUniquePluginName(fRealName); | |||
| else | |||
| pData->name = pData->engine->getUniquePluginName(fLabel); | |||
| // --------------------------------------------------------------- | |||
| // register client | |||
| pData->client = pData->engine->addClient(this); | |||
| if (pData->client == nullptr || ! pData->client->isOk()) | |||
| { | |||
| pData->engine->setLastError("Failed to register plugin client"); | |||
| return false; | |||
| } | |||
| // --------------------------------------------------------------- | |||
| // set default options | |||
| pData->options = 0x0; | |||
| pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; | |||
| pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; | |||
| pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; | |||
| pData->options |= PLUGIN_OPTION_SEND_PITCHBEND; | |||
| pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; | |||
| return true; | |||
| (void)options; | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| private: | |||
| sfzero::Synth fSynth; | |||
| float fNumVoices; | |||
| const char* fLabel; | |||
| const char* fRealName; | |||
| SharedResourcePointer<AutoAudioFormatManager> sAudioFormatManager; | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginSFZero) | |||
| }; | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| CarlaPlugin* CarlaPlugin::newSFZero(const Initializer& init) | |||
| { | |||
| carla_debug("CarlaPluginSFZero::newLinuxSampler({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})", | |||
| init.engine, init.filename, init.name, init.label, init.uniqueId); | |||
| // ------------------------------------------------------------------- | |||
| // Check if file exists | |||
| if (! File(init.filename).existsAsFile()) | |||
| { | |||
| init.engine->setLastError("Requested file is not valid or does not exist"); | |||
| return nullptr; | |||
| } | |||
| CarlaPluginSFZero* const plugin(new CarlaPluginSFZero(init.engine, init.id)); | |||
| if (! plugin->init(init.filename, init.name, init.label, init.options)) | |||
| { | |||
| delete plugin; | |||
| return nullptr; | |||
| } | |||
| return plugin; | |||
| } | |||
| CarlaPlugin* CarlaPlugin::newFileSFZ(const Initializer& init) | |||
| { | |||
| return newSFZero(init); | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| CARLA_BACKEND_END_NAMESPACE | |||