diff --git a/Makefile b/Makefile index dc0b38b65..ccb7acbec 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,7 @@ install: install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/ install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/nekofilter/ install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/zynaddsubfx/ + install -d $(DESTDIR)$(PREFIX)/lib/lv2/carla.lv2/ install -d $(DESTDIR)$(PREFIX)/share/applications/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ @@ -182,6 +183,10 @@ install: # Install python code install -m 755 source/*.py $(DESTDIR)$(PREFIX)/share/carla/ + # Install LV2 plugin + install -m 644 data/lv2/*.ttl $(DESTDIR)$(PREFIX)/lib/lv2/carla.lv2/ + $(LINK) $(PREFIX)/lib/carla/libcarla_standalone.so $(DESTDIR)$(PREFIX)/lib/lv2/carla.lv2/carla.so + # Install resources install -m 644 source/backend/resources/nekofilter-ui $(DESTDIR)$(PREFIX)/lib/carla/resources/ install -m 644 source/backend/resources/nekofilter/*.png $(DESTDIR)$(PREFIX)/lib/carla/resources/nekofilter/ diff --git a/data/lv2/carla.ttl b/data/lv2/carla.ttl new file mode 100644 index 000000000..012ce80db --- /dev/null +++ b/data/lv2/carla.ttl @@ -0,0 +1,53 @@ +@prefix atom: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:requiredFeature , + ; + lv2:extensionData , + ; + +# ui:ui ; + + lv2:port [ + a lv2:InputPort, lv2:AudioPort ; + lv2:index 0 ; + lv2:symbol "audio_in_1" ; + lv2:name "Audio Input 1" ; + ] , + [ + a lv2:InputPort, lv2:AudioPort ; + lv2:index 1 ; + lv2:symbol "audio_in_2" ; + lv2:name "Audio Input 2" ; + ] , + [ + a lv2:OutputPort, lv2:AudioPort ; + lv2:index 2 ; + lv2:symbol "audio_out_1" ; + lv2:name "Audio Output 1" ; + ] , + [ + a lv2:OutputPort, lv2:AudioPort ; + lv2:index 3 ; + lv2:symbol "audio_out_2" ; + lv2:name "Audio Output 2" ; + ] , + [ + a lv2:InputPort, atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports , + ; + lv2:index 4 ; + lv2:symbol "events_in" ; + lv2:name "Events Input" ; + lv2:designation lv2:control ; + ] ; + + doap:name "Carla Plugin" ; + doap:maintainer [ foaf:name "falkTX" ] . diff --git a/data/lv2/manifest.ttl b/data/lv2/manifest.ttl new file mode 100644 index 000000000..22c14caf2 --- /dev/null +++ b/data/lv2/manifest.ttl @@ -0,0 +1,12 @@ +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + + + a ; + ui:binary . diff --git a/resources/ui/carla.ui b/resources/ui/carla.ui index 668ffe83f..262ff4629 100644 --- a/resources/ui/carla.ui +++ b/resources/ui/carla.ui @@ -293,6 +293,7 @@ + @@ -738,6 +739,11 @@ Add New Plugin + + + Export LV2 Plugin State... + + diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 05aade612..fdf9d04ba 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -17,11 +17,33 @@ #ifndef BUILD_BRIDGE +#define WANT_LV2 + #include "CarlaEngineInternal.hpp" #include "CarlaStateUtils.hpp" #include "CarlaNative.hpp" +#ifdef WANT_LV2 +# include "lv2/lv2.h" +# include "lv2/atom.h" +struct Lv2HostDescriptor { + uint32_t bufferSize; + double sampleRate; + TimeInfo timeInfo; + float* audioPorts[4]; + LV2_Atom_Sequence* atomPort; + + Lv2HostDescriptor() + : bufferSize(0), + sampleRate(0.0), + audioPorts{nullptr}, + atomPort(nullptr) {} +}; +#else +struct Lv2HostDescriptor; +#endif + #include CARLA_BACKEND_START_NAMESPACE @@ -32,9 +54,10 @@ class CarlaEngineNative : public PluginDescriptorClass, public CarlaEngine { public: - CarlaEngineNative(const HostDescriptor* const host) + CarlaEngineNative(const HostDescriptor* const host, Lv2HostDescriptor* const lv2Host = nullptr) : PluginDescriptorClass(host), - CarlaEngine() + CarlaEngine(), + fLv2Host(lv2Host) { carla_debug("CarlaEngineNative::CarlaEngineNative()"); @@ -54,6 +77,14 @@ public: setAboutToClose(); removeAllPlugins(); close(); + +#ifdef WANT_LV2 + if (fLv2Host != nullptr) + { + delete fLv2Host; + delete hostHandle(); + } +#endif } protected: @@ -74,9 +105,9 @@ protected: bool close() override { carla_debug("CarlaEngineNative::close()"); - CarlaEngine::close(); - return true; + proccessPendingEvents(); + return CarlaEngine::close(); } bool isRunning() const override @@ -308,10 +339,20 @@ protected: plugin->setActive(false, true, false); } + + // just in case + proccessPendingEvents(); } - void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const uint32_t midiEventCount, const MidiEvent* const midiEvents) override + void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const uint32_t midiEventCount, const ::MidiEvent* const midiEvents) override { + if (kData->curPluginCount == 0) + { + carla_zeroFloat(outBuffer[0], frames); + carla_zeroFloat(outBuffer[1], frames); + return CarlaEngine::proccessPendingEvents(); + } + // --------------------------------------------------------------- // Time Info @@ -339,28 +380,77 @@ protected: } // --------------------------------------------------------------- - // Initial checks + // initialize input events - if (kData->curPluginCount == 0) + carla_zeroStruct(kData->rack.in, RACK_EVENT_COUNT); { - carla_zeroFloat(outBuffer[0], frames); - carla_zeroFloat(outBuffer[1], frames); - return CarlaEngine::proccessPendingEvents(); + uint32_t engineEventIndex = 0; + + for (uint32_t i=0; i < midiEventCount && engineEventIndex < RACK_EVENT_COUNT; ++i) + { + const ::MidiEvent& midiEvent(midiEvents[i]); + + if (midiEvent.size > 4) + continue; + + const uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent.data); + const uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent.data); + + // we don't want some events + if (status == MIDI_STATUS_PROGRAM_CHANGE) + continue; + + // handle note/sound off properly + if (status == MIDI_STATUS_CONTROL_CHANGE) + { + const uint8_t control = midiEvent.data[1]; + + if (MIDI_IS_CONTROL_BANK_SELECT(control)) + continue; + + if (control == MIDI_CONTROL_ALL_SOUND_OFF || control == MIDI_CONTROL_ALL_NOTES_OFF) + { + EngineEvent& engineEvent(kData->rack.in[engineEventIndex++]); + engineEvent.clear(); + + engineEvent.type = kEngineEventTypeControl; + engineEvent.time = midiEvent.time; + engineEvent.channel = channel; + + engineEvent.ctrl.type = (control == MIDI_CONTROL_ALL_SOUND_OFF) ? kEngineControlEventTypeAllSoundOff : kEngineControlEventTypeAllNotesOff; + engineEvent.ctrl.param = 0; + engineEvent.ctrl.value = 0.0f; + + continue; + } + } + + EngineEvent& engineEvent(kData->rack.in[engineEventIndex++]); + engineEvent.clear(); + + engineEvent.type = kEngineEventTypeMidi; + engineEvent.time = midiEvent.time; + engineEvent.channel = channel; + + engineEvent.midi.data[0] = MIDI_GET_STATUS_FROM_DATA(midiEvent.data); + engineEvent.midi.data[1] = midiEvent.data[1]; + engineEvent.midi.data[2] = midiEvent.data[2]; + engineEvent.midi.data[3] = midiEvent.data[3]; + engineEvent.midi.size = midiEvent.size; + } } + // --------------------------------------------------------------- // create audio buffers + float* inBuf[2] = { inBuffer[0], inBuffer[1] }; float* outBuf[2] = { outBuffer[0], outBuffer[1] }; + // --------------------------------------------------------------- // process - CarlaEngine::processRack(inBuf, outBuf, frames); + CarlaEngine::processRack(inBuf, outBuf, frames); CarlaEngine::proccessPendingEvents(); - return; - - // unused - (void)midiEventCount; - (void)midiEvents; } // ------------------------------------------------------------------- @@ -383,13 +473,15 @@ protected: void uiSetParameterValue(const uint32_t index, const float value) override { - CARLA_ASSERT(index < getParameterCount()); - return; + if (index >= getParameterCount()) + return; - // TODO + CarlaPlugin* const plugin(kData->plugins[0].plugin); - // unused - (void)value; + if (plugin == nullptr || ! plugin->enabled()) + return; + + plugin->uiParameterChange(index, value); } void uiSetMidiProgram(const uint8_t channel, const uint32_t bank, const uint32_t program) override @@ -501,6 +593,120 @@ protected: } } + // ------------------------------------------------------------------- + +#ifdef WANT_LV2 + Lv2HostDescriptor* const fLv2Host; + + // ------------------------------------------------------------------- + +public: + #define handlePtr ((Lv2HostDescriptor*)handle) + + static uint32_t lv2host_get_buffer_size(HostHandle handle) + { + return handlePtr->bufferSize; + } + + static double lv2host_get_sample_rate(HostHandle handle) + { + return handlePtr->sampleRate; + } + + static const TimeInfo* lv2host_get_time_info(HostHandle handle) + { + return &handlePtr->timeInfo; + } + + static bool lv2host_write_midi_event(HostHandle, const MidiEvent*) + { + // MIDI Out not supported yet + return false; + } + + static void lv2host_ui_parameter_changed(HostHandle, uint32_t, float) {} + static void lv2host_ui_midi_program_changed(HostHandle, uint8_t, uint32_t, uint32_t) {} + static void lv2host_ui_custom_data_changed(HostHandle, const char*, const char*) {} + static void lv2host_ui_closed(HostHandle) {} + static const char* lv2host_ui_open_file(HostHandle, bool, const char*, const char*) { return nullptr; } + static const char* lv2host_ui_save_file(HostHandle, bool, const char*, const char*) { return nullptr; } + static intptr_t lv2host_dispatcher(HostHandle, HostDispatcherOpcode, int32_t, intptr_t, void*) { return 0; } + + #undef handlePtr + + // ------------------------------------------------------------------- + + #define handlePtr ((CarlaEngineNative*)handle) + + static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, const char*, const LV2_Feature* const* /*features*/) + { + Lv2HostDescriptor* const lv2Host(new Lv2HostDescriptor()); + lv2Host->bufferSize = 1024; // TODO + lv2Host->sampleRate = sampleRate; + lv2Host->timeInfo.frame = 0; + lv2Host->timeInfo.usecs = 0; + lv2Host->timeInfo.playing = false; + lv2Host->timeInfo.bbt.valid = false; + + HostDescriptor* const host(new HostDescriptor); + host->handle = lv2Host; + host->ui_name = nullptr; + + host->get_buffer_size = lv2host_get_buffer_size; + host->get_sample_rate = lv2host_get_sample_rate; + host->get_time_info = lv2host_get_time_info; + host->write_midi_event = lv2host_write_midi_event; + host->ui_parameter_changed = lv2host_ui_parameter_changed; + host->ui_custom_data_changed = lv2host_ui_custom_data_changed; + host->ui_closed = lv2host_ui_closed; + host->ui_open_file = lv2host_ui_open_file; + host->ui_save_file = lv2host_ui_save_file; + host->dispatcher = lv2host_dispatcher; + + return new CarlaEngineNative(host, lv2Host); + } + + static void lv2_connect_port(LV2_Handle handle, uint32_t port, void* dataLocation) + { + if (port < 4) + handlePtr->fLv2Host->audioPorts[port] = (float*)dataLocation; + else if (port == 4) + handlePtr->fLv2Host->atomPort = (LV2_Atom_Sequence*)dataLocation; + } + + static void lv2_activate(LV2_Handle handle) + { + handlePtr->activate(); + } + + static void lv2_run(LV2_Handle handle, uint32_t sampleCount) + { + float* inBuffer[2] = { handlePtr->fLv2Host->audioPorts[0], handlePtr->fLv2Host->audioPorts[1] }; + float* outBuffer[2] = { handlePtr->fLv2Host->audioPorts[2], handlePtr->fLv2Host->audioPorts[3] }; + + // TODO - get midiEvents and timePos from atomPort + + handlePtr->process(inBuffer, outBuffer, sampleCount, 0, nullptr); + } + + static void lv2_deactivate(LV2_Handle handle) + { + handlePtr->deactivate(); + } + + static void lv2_cleanup(LV2_Handle handle) + { + delete handlePtr; + } + + static const void* lv2_extension_data(const char* /*uri*/) + { + return nullptr; + } + + #undef handlePtr +#endif + private: PluginDescriptorClassEND(CarlaEngineNative) CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineNative) @@ -524,6 +730,19 @@ static const PluginDescriptor carlaDesc = { PluginDescriptorFILL(CarlaEngineNative) }; +#ifdef WANT_LV2 +static const LV2_Descriptor carlaLv2Desc = { + /* URI */ "http://kxstudio.sf.net/carla", + /* instantiate */ CarlaEngineNative::lv2_instantiate, + /* connect_port */ CarlaEngineNative::lv2_connect_port, + /* activate */ CarlaEngineNative::lv2_activate, + /* run */ CarlaEngineNative::lv2_run, + /* deactivate */ CarlaEngineNative::lv2_deactivate, + /* cleanup */ CarlaEngineNative::lv2_cleanup, + /* extension_data */ CarlaEngineNative::lv2_extension_data +}; +#endif + void CarlaEngine::registerNativePlugin() { carla_register_native_plugin(&carlaDesc); @@ -533,4 +752,15 @@ CARLA_BACKEND_END_NAMESPACE // ----------------------------------------------------------------------- +#ifdef WANT_LV2 +CARLA_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + CARLA_BACKEND_USE_NAMESPACE; + return (index == 0) ? &carlaLv2Desc : nullptr; +} +#endif + +// ----------------------------------------------------------------------- + #endif // ! BUILD_BRIDGE diff --git a/source/backend/engine/CarlaEnginePlugin.cpp b/source/backend/engine/CarlaEnginePlugin.cpp index 96cffe3af..b6317a34e 100644 --- a/source/backend/engine/CarlaEnginePlugin.cpp +++ b/source/backend/engine/CarlaEnginePlugin.cpp @@ -18,8 +18,7 @@ #ifdef WANT_PLUGIN #include "CarlaEngineInternal.hpp" -#include "CarlaBackendUtils.hpp" -#include "CarlaMIDI.h" +#include "CarlaStateUtils.hpp" #include "DistrhoPlugin.hpp" @@ -31,47 +30,47 @@ CARLA_BACKEND_START_NAMESPACE // ----------------------------------------- // Parameters -static const unsigned char paramMap[] = { +static const unsigned char kParamMap[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F }; -static const unsigned int paramVolume = 5; -static const unsigned int paramBalance = 6; -static const unsigned int paramPan = 8; +static const unsigned int kParamVolume = 5; +static const unsigned int kParamBalance = 6; +static const unsigned int kParamPan = 8; -static const unsigned int paramCount = sizeof(paramMap); -static const unsigned int programCount = 128; -static const unsigned int stateCount = MAX_RACK_PLUGINS; +static const unsigned int kParamCount = sizeof(paramMap); +static const unsigned int kProgramCount = 128; +static const unsigned int kStateCount = MAX_RACK_PLUGINS; // ----------------------------------------- -class CarlaEnginePlugin : public CarlaEngine, - public DISTRHO::Plugin +class CarlaEnginePlugin : public DISTRHO::Plugin, + public CarlaEngine { public: CarlaEnginePlugin() - : CarlaEngine(), - DISTRHO::Plugin(paramCount, programCount, stateCount), - paramBuffers{0.0f}, - prevParamBuffers{0.0f} + : DISTRHO::Plugin(kParamCount, kProgramCount, kStateCount), + CarlaEngine(), + fParamBuffers{0.0f}, + fPrevParamBuffers{0.0f} { carla_debug("CarlaEnginePlugin::CarlaEnginePlugin()"); // init parameters - paramBuffers[paramVolume] = 100.0f; - paramBuffers[paramBalance] = 63.5f; - paramBuffers[paramPan] = 63.5f; + fParamBuffers[kParamVolume] = 100.0f; + fParamBuffers[kParamBalance] = 63.5f; + fParamBuffers[kParamPan] = 63.5f; - prevParamBuffers[paramVolume] = 100.0f; - prevParamBuffers[paramBalance] = 63.5f; - prevParamBuffers[paramPan] = 63.5f; + fPrevParamBuffers[kParamVolume] = 100.0f; + fPrevParamBuffers[kParamBalance] = 63.5f; + fPrevParamBuffers[kParamPan] = 63.5f; // set-up engine fOptions.processMode = PROCESS_MODE_CONTINUOUS_RACK; - fOptions.transportMode = TRANSPORT_MODE_INTERNAL; + fOptions.transportMode = TRANSPORT_MODE_PLUGIN; fOptions.forceStereo = true; fOptions.preferPluginBridges = false; fOptions.preferUiBridges = false; @@ -89,7 +88,7 @@ public: protected: // ------------------------------------- - // CarlaEngine virtual calls + // Carla Engine virtual calls bool init(const char* const clientName) override { @@ -105,9 +104,9 @@ protected: bool close() override { carla_debug("CarlaEnginePlugin::close()"); - CarlaEngine::close(); - return true; + proccessPendingEvents(); + return CarlaEngine::close(); } bool isRunning() const override @@ -158,7 +157,7 @@ protected: void d_initParameter(uint32_t index, DISTRHO::Parameter& parameter) override { - if (index >= paramCount) + if (index >= kParamCount) return; parameter.hints = DISTRHO::PARAMETER_IS_AUTOMABLE; @@ -166,14 +165,7 @@ protected: parameter.ranges.min = 0.0f; parameter.ranges.max = 127.0f; - if (index == paramVolume) - parameter.ranges.def = 100.0f; - else if (index == paramBalance) - parameter.ranges.def = 63.5f; - else if (index == paramPan) - parameter.ranges.def = 63.5f; - - switch (paramMap[index]) + switch (kParamMap[index]) { case 0x01: parameter.name = "0x01 Modulation"; @@ -192,15 +184,18 @@ protected: break; case 0x07: parameter.name = "0x07 Volume"; + parameter.ranges.def = 100.0f; break; case 0x08: parameter.name = "0x08 Balance"; + parameter.ranges.def = 63.5f; break; case 0x09: parameter.name = "0x09 (Undefined)"; break; case 0x0A: parameter.name = "0x0A Pan"; + parameter.ranges.def = 63.5f; break; case 0x0B: parameter.name = "0x0B Expression"; @@ -325,6 +320,9 @@ protected: case 0x5F: parameter.name = "0x5F FX 5 Depth [Phaser]"; break; + default: + parameter.name = ""; + break; } } @@ -343,39 +341,41 @@ protected: float d_parameterValue(uint32_t index) override { - if (index >= paramCount) + if (index >= kParamCount) return 0.0f; - return paramBuffers[index]; + return fParamBuffers[index]; } void d_setParameterValue(uint32_t index, float value) override { - if (index >= paramCount) + if (index >= kParamCount) return; - paramBuffers[index] = value; + fParamBuffers[index] = value; } void d_setProgram(uint32_t index) override { - if (index >= programCount) + if (index >= kProgramCount) return; - if (kData->curPluginCount == 0) + if (kData->curPluginCount == 0 || kData->plugins == nullptr) return; - if (CarlaPlugin* const plugin = getPlugin(0)) + CarlaPlugin* const plugin(kData->plugins[0].plugin); + + if (plugin == nullptr || ! plugin->enabled()) + return; + + if (plugin->midiProgramCount() > 0) { - if (plugin->programCount() > 0) - { - if (index <= plugin->programCount()) - plugin->setProgram(index, true, true, false); - } - else if (plugin->midiProgramCount()) - { - if (index <= plugin->midiProgramCount()) - plugin->setMidiProgram(index, true, true, false); - } + if (index <= plugin->midiProgramCount()) + plugin->setMidiProgram(index, true, true, false); + } + else if (plugin->programCount() > 0 && plugin->type() != PLUGIN_LV2) + { + if (index <= plugin->programCount()) + plugin->setProgram(index, true, true, false); } } @@ -391,17 +391,6 @@ protected: void d_activate() override { - static bool firstTestInit = true; - - if (firstTestInit) - { - firstTestInit = false; - if (! addPlugin(PLUGIN_INTERNAL, nullptr, nullptr, "zynaddsubfx")) - carla_stderr2("Plugin add zynaddsubfx failed"); - if (! addPlugin(PLUGIN_INTERNAL, nullptr, nullptr, "PingPongPan")) - carla_stderr2("Plugin add Pan failed"); - } - for (unsigned int i=0; i < kData->curPluginCount; ++i) { CarlaPlugin* const plugin(getPluginUnchecked(i)); @@ -410,7 +399,7 @@ protected: plugin->setActive(true, true, false); } - carla_copyFloat(prevParamBuffers, paramBuffers, paramCount); + carla_copyFloat(fPrevParamBuffers, fParamBuffers, kParamCount); } void d_deactivate() override @@ -422,6 +411,9 @@ protected: if (plugin != nullptr && plugin->enabled()) plugin->setActive(false, true, false); } + + // just in case + proccessPendingEvents(); } void d_run(float** inputs, float** outputs, uint32_t frames, uint32_t midiEventCount, const DISTRHO::MidiEvent* midiEvents) override @@ -433,59 +425,97 @@ protected: return proccessPendingEvents(); } + // --------------------------------------------------------------- // create audio buffers + float* inBuf[2] = { inputs[0], inputs[1] }; float* outBuf[2] = { outputs[0], outputs[1] }; + // --------------------------------------------------------------- // initialize input events + carla_zeroStruct(kData->rack.in, RACK_EVENT_COUNT); { uint32_t engineEventIndex = 0; - for (unsigned int i=0; i < paramCount && engineEventIndex+midiEventCount < RACK_EVENT_COUNT; ++i) + for (unsigned int i=0; i < kParamCount && engineEventIndex+midiEventCount < RACK_EVENT_COUNT; ++i) { - if (paramBuffers[i] == prevParamBuffers[i]) + if (fParamBuffers[i] == fPrevParamBuffers[i]) continue; - EngineEvent* const engineEvent = &kData->rack.in[engineEventIndex++]; - engineEvent->clear(); + EngineEvent& engineEvent(kData->rack.in[engineEventIndex++]); + engineEvent.clear(); - engineEvent->type = kEngineEventTypeControl; - engineEvent->time = 0; - engineEvent->channel = 0; + engineEvent.type = kEngineEventTypeControl; + engineEvent.time = 0; + engineEvent.channel = 0; - engineEvent->ctrl.type = kEngineControlEventTypeParameter; - engineEvent->ctrl.param = paramMap[i]; - engineEvent->ctrl.value = paramBuffers[i]/127.0f; + engineEvent.ctrl.type = kEngineControlEventTypeParameter; + engineEvent.ctrl.param = kParamMap[i]; + engineEvent.ctrl.value = fParamBuffers[i]/127.0f; - prevParamBuffers[i] = paramBuffers[i]; + fPrevParamBuffers[i] = fParamBuffers[i]; } - const DISTRHO::MidiEvent* midiEvent; - for (uint32_t i=0; i < midiEventCount && engineEventIndex < RACK_EVENT_COUNT; ++i) { - midiEvent = &midiEvents[i]; + const DISTRHO::MidiEvent& midiEvent(midiEvents[i]); + + if (midiEvent.size > 4) + continue; + + const uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent.buf); + const uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent.buf); - if (midiEvent->size > 4) + // we don't want some events + if (status == MIDI_STATUS_PROGRAM_CHANGE) continue; - EngineEvent* const engineEvent = &kData->rack.in[engineEventIndex++]; - engineEvent->clear(); + // handle note/sound off properly + if (status == MIDI_STATUS_CONTROL_CHANGE) + { + const uint8_t control = midiEvent.buf[1]; + + if (MIDI_IS_CONTROL_BANK_SELECT(control)) + continue; + + if (control == MIDI_CONTROL_ALL_SOUND_OFF || control == MIDI_CONTROL_ALL_NOTES_OFF) + { + EngineEvent& engineEvent(kData->rack.in[engineEventIndex++]); + engineEvent.clear(); + + engineEvent.type = kEngineEventTypeControl; + engineEvent.time = midiEvent.frame; + engineEvent.channel = channel; - engineEvent->type = kEngineEventTypeMidi; - engineEvent->time = midiEvent->frame; - engineEvent->channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent->buf); + engineEvent.ctrl.type = (control == MIDI_CONTROL_ALL_SOUND_OFF) ? kEngineControlEventTypeAllSoundOff : kEngineControlEventTypeAllNotesOff; + engineEvent.ctrl.param = 0; + engineEvent.ctrl.value = 0.0f; - engineEvent->midi.data[0] = MIDI_GET_STATUS_FROM_DATA(midiEvent->buf); - engineEvent->midi.data[1] = midiEvent->buf[1]; - engineEvent->midi.data[2] = midiEvent->buf[2]; - engineEvent->midi.data[3] = midiEvent->buf[3]; - engineEvent->midi.size = midiEvent->size; + continue; + } + } + + EngineEvent& engineEvent(kData->rack.in[engineEventIndex++]); + engineEvent.clear(); + + engineEvent.type = kEngineEventTypeMidi; + engineEvent.time = midiEvent.frame; + engineEvent.channel = channel; + + engineEvent.midi.data[0] = MIDI_GET_STATUS_FROM_DATA(midiEvent.buf); + engineEvent.midi.data[1] = midiEvent.buf[1]; + engineEvent.midi.data[2] = midiEvent.buf[2]; + engineEvent.midi.data[3] = midiEvent.buf[3]; + engineEvent.midi.size = midiEvent.size; } } + // --------------------------------------------------------------- + // process + processRack(inBuf, outBuf, frames); + proccessPendingEvents(); } // --------------------------------------------- @@ -512,8 +542,8 @@ protected: // --------------------------------------------- private: - float paramBuffers[paramCount]; - float prevParamBuffers[paramCount]; + float fParamBuffers[kParamCount]; + float fPrevParamBuffers[kParamCount]; }; CARLA_BACKEND_END_NAMESPACE @@ -533,4 +563,4 @@ END_NAMESPACE_DISTRHO #include "DistrhoPluginMain.cpp" -#endif // CARLA_ENGINE_PLUGIN +#endif // WANT_PLUGIN diff --git a/source/backend/engine/CarlaEngineRtAudio.cpp b/source/backend/engine/CarlaEngineRtAudio.cpp index ff15694b9..f7672c461 100644 --- a/source/backend/engine/CarlaEngineRtAudio.cpp +++ b/source/backend/engine/CarlaEngineRtAudio.cpp @@ -350,7 +350,7 @@ public: fMidiInEvents.clear(); //fMidiOutEvents.clear(); - return true; + return (! hasError); } bool isRunning() const override diff --git a/source/carla.py b/source/carla.py index 34c2abcda..d6ce120eb 100755 --- a/source/carla.py +++ b/source/carla.py @@ -761,6 +761,7 @@ class CarlaMainW(QMainWindow): self.connect(self.ui.act_file_open, SIGNAL("triggered()"), SLOT("slot_fileOpen()")) self.connect(self.ui.act_file_save, SIGNAL("triggered()"), SLOT("slot_fileSave()")) self.connect(self.ui.act_file_save_as, SIGNAL("triggered()"), SLOT("slot_fileSaveAs()")) + self.connect(self.ui.act_file_export_lv2, SIGNAL("triggered()"), SLOT("slot_fileExportLv2Preset()")) self.connect(self.ui.act_engine_start, SIGNAL("triggered()"), SLOT("slot_engineStart()")) self.connect(self.ui.act_engine_stop, SIGNAL("triggered()"), SLOT("slot_engineStop()")) @@ -1361,7 +1362,7 @@ class CarlaMainW(QMainWindow): filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter) if filenameTry: - # FIXME - show dialog to user + # FIXME - show dialog to user (remove all plugins?) self.removeAllPlugins() self.loadProject(filenameTry) @@ -1373,16 +1374,70 @@ class CarlaMainW(QMainWindow): fileFilter = self.tr("Carla Project File (*.carxp)") filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter) - if filenameTry: - if not filenameTry.endswith(".carxp"): - filenameTry += ".carxp" + if not filenameTry: + return - self.saveProject(filenameTry) + if not filenameTry.endswith(".carxp"): + filenameTry += ".carxp" + + self.saveProject(filenameTry) @pyqtSlot() def slot_fileSaveAs(self): self.slot_fileSave(True) + @pyqtSlot() + def slot_fileExportLv2Preset(self): + fileFilter = self.tr("LV2 Preset (*.lv2)") + filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter) + + if not filenameTry: + return + + if not filenameTry.endswith(".lv2"): + filenameTry += ".lv2" + + if os.path.exists(filenameTry) and not os.path.isdir(filenameTry): + # TODO - error + return + + # Save current project to a tmp file, and read it + tmpFile = os.path.join(TMP, "carla-plugin-export.carxp") + + if not Carla.host.save_project(tmpFile): + # TODO - error + return + + tmpFileFd = open(tmpFile, "r") + presetContents = tmpFileFd.read() + tmpFileFd.close() + os.remove(tmpFile) + + # Create LV2 Preset + os.mkdir(filenameTry) + + manifestPath = os.path.join(filenameTry, "manifest.ttl") + + manifestFd = open(manifestPath, "w") + manifestFd.write("""# LV2 Preset for the Carla LV2 Plugin +@prefix lv2: . +@prefix pset: . +@prefix rdfs: . +@prefix state: . + + + a pset:Preset ; + lv2:appliesTo ; + rdfs:label "%s" ; + state:state [ + +\"\"\" +%s +\"\"\" + ] . +""" % (manifestPath, os.path.basename(filenameTry), presetContents)) + manifestFd.close() + @pyqtSlot() def slot_loadProjectLater(self): self.fProjectLoading = True