| @@ -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 | |||||