| @@ -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/ | |||
| @@ -0,0 +1,53 @@ | |||
| @prefix atom: <http://lv2plug.in/ns/ext/atom#> . | |||
| @prefix doap: <http://usefulinc.com/ns/doap#> . | |||
| @prefix foaf: <http://xmlns.com/foaf/0.1/> . | |||
| @prefix lv2: <http://lv2plug.in/ns/lv2core#> . | |||
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | |||
| @prefix ui: <http://lv2plug.in/ns/extensions/ui#> . | |||
| <http://kxstudio.sf.net/carla> | |||
| a lv2:Plugin ; | |||
| lv2:requiredFeature <http://lv2plug.in/ns/ext/buf-size#boundedBlockLength> , | |||
| <http://lv2plug.in/ns/ext/urid#map> ; | |||
| lv2:extensionData <http://lv2plug.in/ns/ext/options#interface> , | |||
| <http://lv2plug.in/ns/ext/state#interface> ; | |||
| # ui:ui <http://kxstudio.sf.net/carla#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 <http://lv2plug.in/ns/ext/midi#MidiEvent> , | |||
| <http://lv2plug.in/ns/ext/time#Position> ; | |||
| lv2:index 4 ; | |||
| lv2:symbol "events_in" ; | |||
| lv2:name "Events Input" ; | |||
| lv2:designation lv2:control ; | |||
| ] ; | |||
| doap:name "Carla Plugin" ; | |||
| doap:maintainer [ foaf:name "falkTX" ] . | |||
| @@ -0,0 +1,12 @@ | |||
| @prefix lv2: <http://lv2plug.in/ns/lv2core#> . | |||
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | |||
| @prefix ui: <http://lv2plug.in/ns/extensions/ui#> . | |||
| <http://kxstudio.sf.net/carla> | |||
| a lv2:Plugin ; | |||
| lv2:binary <carla.so> ; | |||
| rdfs:seeAlso <carla.ttl> . | |||
| <http://kxstudio.sf.net/carla#ui> | |||
| a <http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget> ; | |||
| ui:binary <carla.so> . | |||
| @@ -293,6 +293,7 @@ | |||
| <addaction name="act_file_open"/> | |||
| <addaction name="act_file_save"/> | |||
| <addaction name="act_file_save_as"/> | |||
| <addaction name="act_file_export_lv2"/> | |||
| <addaction name="separator"/> | |||
| <addaction name="act_file_quit"/> | |||
| </widget> | |||
| @@ -738,6 +739,11 @@ | |||
| <string>Add New Plugin</string> | |||
| </property> | |||
| </action> | |||
| <action name="act_file_export_lv2"> | |||
| <property name="text"> | |||
| <string>Export LV2 Plugin State...</string> | |||
| </property> | |||
| </action> | |||
| </widget> | |||
| <customwidgets> | |||
| <customwidget> | |||
| @@ -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 <QtCore/QTextStream> | |||
| 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<EngineEvent>(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 | |||
| @@ -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<EngineEvent>(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 | |||
| @@ -350,7 +350,7 @@ public: | |||
| fMidiInEvents.clear(); | |||
| //fMidiOutEvents.clear(); | |||
| return true; | |||
| return (! hasError); | |||
| } | |||
| bool isRunning() const override | |||
| @@ -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: <http://lv2plug.in/ns/lv2core#> . | |||
| @prefix pset: <http://lv2plug.in/ns/ext/presets#> . | |||
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | |||
| @prefix state: <http://lv2plug.in/ns/ext/state#> . | |||
| <file://%s> | |||
| a pset:Preset ; | |||
| lv2:appliesTo <http://kxstudio.sf.net/carla> ; | |||
| rdfs:label "%s" ; | |||
| state:state [ | |||
| <http://kxstudio.sf.net/ns/carla/string> | |||
| \"\"\" | |||
| %s | |||
| \"\"\" | |||
| ] . | |||
| """ % (manifestPath, os.path.basename(filenameTry), presetContents)) | |||
| manifestFd.close() | |||
| @pyqtSlot() | |||
| def slot_loadProjectLater(self): | |||
| self.fProjectLoading = True | |||