@@ -134,6 +134,7 @@ install: | |||||
install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/ | install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/ | ||||
install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/nekofilter/ | install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/nekofilter/ | ||||
install -d $(DESTDIR)$(PREFIX)/lib/carla/resources/zynaddsubfx/ | 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/applications/ | ||||
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ | install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ | ||||
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ | install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ | ||||
@@ -182,6 +183,10 @@ install: | |||||
# Install python code | # Install python code | ||||
install -m 755 source/*.py $(DESTDIR)$(PREFIX)/share/carla/ | 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 resources | ||||
install -m 644 source/backend/resources/nekofilter-ui $(DESTDIR)$(PREFIX)/lib/carla/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/ | 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_open"/> | ||||
<addaction name="act_file_save"/> | <addaction name="act_file_save"/> | ||||
<addaction name="act_file_save_as"/> | <addaction name="act_file_save_as"/> | ||||
<addaction name="act_file_export_lv2"/> | |||||
<addaction name="separator"/> | <addaction name="separator"/> | ||||
<addaction name="act_file_quit"/> | <addaction name="act_file_quit"/> | ||||
</widget> | </widget> | ||||
@@ -738,6 +739,11 @@ | |||||
<string>Add New Plugin</string> | <string>Add New Plugin</string> | ||||
</property> | </property> | ||||
</action> | </action> | ||||
<action name="act_file_export_lv2"> | |||||
<property name="text"> | |||||
<string>Export LV2 Plugin State...</string> | |||||
</property> | |||||
</action> | |||||
</widget> | </widget> | ||||
<customwidgets> | <customwidgets> | ||||
<customwidget> | <customwidget> | ||||
@@ -17,11 +17,33 @@ | |||||
#ifndef BUILD_BRIDGE | #ifndef BUILD_BRIDGE | ||||
#define WANT_LV2 | |||||
#include "CarlaEngineInternal.hpp" | #include "CarlaEngineInternal.hpp" | ||||
#include "CarlaStateUtils.hpp" | #include "CarlaStateUtils.hpp" | ||||
#include "CarlaNative.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> | #include <QtCore/QTextStream> | ||||
CARLA_BACKEND_START_NAMESPACE | CARLA_BACKEND_START_NAMESPACE | ||||
@@ -32,9 +54,10 @@ class CarlaEngineNative : public PluginDescriptorClass, | |||||
public CarlaEngine | public CarlaEngine | ||||
{ | { | ||||
public: | public: | ||||
CarlaEngineNative(const HostDescriptor* const host) | |||||
CarlaEngineNative(const HostDescriptor* const host, Lv2HostDescriptor* const lv2Host = nullptr) | |||||
: PluginDescriptorClass(host), | : PluginDescriptorClass(host), | ||||
CarlaEngine() | |||||
CarlaEngine(), | |||||
fLv2Host(lv2Host) | |||||
{ | { | ||||
carla_debug("CarlaEngineNative::CarlaEngineNative()"); | carla_debug("CarlaEngineNative::CarlaEngineNative()"); | ||||
@@ -54,6 +77,14 @@ public: | |||||
setAboutToClose(); | setAboutToClose(); | ||||
removeAllPlugins(); | removeAllPlugins(); | ||||
close(); | close(); | ||||
#ifdef WANT_LV2 | |||||
if (fLv2Host != nullptr) | |||||
{ | |||||
delete fLv2Host; | |||||
delete hostHandle(); | |||||
} | |||||
#endif | |||||
} | } | ||||
protected: | protected: | ||||
@@ -74,9 +105,9 @@ protected: | |||||
bool close() override | bool close() override | ||||
{ | { | ||||
carla_debug("CarlaEngineNative::close()"); | carla_debug("CarlaEngineNative::close()"); | ||||
CarlaEngine::close(); | |||||
return true; | |||||
proccessPendingEvents(); | |||||
return CarlaEngine::close(); | |||||
} | } | ||||
bool isRunning() const override | bool isRunning() const override | ||||
@@ -308,10 +339,20 @@ protected: | |||||
plugin->setActive(false, true, false); | 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 | // 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 | // create audio buffers | ||||
float* inBuf[2] = { inBuffer[0], inBuffer[1] }; | float* inBuf[2] = { inBuffer[0], inBuffer[1] }; | ||||
float* outBuf[2] = { outBuffer[0], outBuffer[1] }; | float* outBuf[2] = { outBuffer[0], outBuffer[1] }; | ||||
// --------------------------------------------------------------- | |||||
// process | // process | ||||
CarlaEngine::processRack(inBuf, outBuf, frames); | |||||
CarlaEngine::processRack(inBuf, outBuf, frames); | |||||
CarlaEngine::proccessPendingEvents(); | CarlaEngine::proccessPendingEvents(); | ||||
return; | |||||
// unused | |||||
(void)midiEventCount; | |||||
(void)midiEvents; | |||||
} | } | ||||
// ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
@@ -383,13 +473,15 @@ protected: | |||||
void uiSetParameterValue(const uint32_t index, const float value) override | 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 | 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: | private: | ||||
PluginDescriptorClassEND(CarlaEngineNative) | PluginDescriptorClassEND(CarlaEngineNative) | ||||
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineNative) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineNative) | ||||
@@ -524,6 +730,19 @@ static const PluginDescriptor carlaDesc = { | |||||
PluginDescriptorFILL(CarlaEngineNative) | 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() | void CarlaEngine::registerNativePlugin() | ||||
{ | { | ||||
carla_register_native_plugin(&carlaDesc); | 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 | #endif // ! BUILD_BRIDGE |
@@ -18,8 +18,7 @@ | |||||
#ifdef WANT_PLUGIN | #ifdef WANT_PLUGIN | ||||
#include "CarlaEngineInternal.hpp" | #include "CarlaEngineInternal.hpp" | ||||
#include "CarlaBackendUtils.hpp" | |||||
#include "CarlaMIDI.h" | |||||
#include "CarlaStateUtils.hpp" | |||||
#include "DistrhoPlugin.hpp" | #include "DistrhoPlugin.hpp" | ||||
@@ -31,47 +30,47 @@ CARLA_BACKEND_START_NAMESPACE | |||||
// ----------------------------------------- | // ----------------------------------------- | ||||
// Parameters | // 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, | 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, | 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, | 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, | ||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F | 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: | public: | ||||
CarlaEnginePlugin() | 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()"); | carla_debug("CarlaEnginePlugin::CarlaEnginePlugin()"); | ||||
// init parameters | // 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 | // set-up engine | ||||
fOptions.processMode = PROCESS_MODE_CONTINUOUS_RACK; | fOptions.processMode = PROCESS_MODE_CONTINUOUS_RACK; | ||||
fOptions.transportMode = TRANSPORT_MODE_INTERNAL; | |||||
fOptions.transportMode = TRANSPORT_MODE_PLUGIN; | |||||
fOptions.forceStereo = true; | fOptions.forceStereo = true; | ||||
fOptions.preferPluginBridges = false; | fOptions.preferPluginBridges = false; | ||||
fOptions.preferUiBridges = false; | fOptions.preferUiBridges = false; | ||||
@@ -89,7 +88,7 @@ public: | |||||
protected: | protected: | ||||
// ------------------------------------- | // ------------------------------------- | ||||
// CarlaEngine virtual calls | |||||
// Carla Engine virtual calls | |||||
bool init(const char* const clientName) override | bool init(const char* const clientName) override | ||||
{ | { | ||||
@@ -105,9 +104,9 @@ protected: | |||||
bool close() override | bool close() override | ||||
{ | { | ||||
carla_debug("CarlaEnginePlugin::close()"); | carla_debug("CarlaEnginePlugin::close()"); | ||||
CarlaEngine::close(); | |||||
return true; | |||||
proccessPendingEvents(); | |||||
return CarlaEngine::close(); | |||||
} | } | ||||
bool isRunning() const override | bool isRunning() const override | ||||
@@ -158,7 +157,7 @@ protected: | |||||
void d_initParameter(uint32_t index, DISTRHO::Parameter& parameter) override | void d_initParameter(uint32_t index, DISTRHO::Parameter& parameter) override | ||||
{ | { | ||||
if (index >= paramCount) | |||||
if (index >= kParamCount) | |||||
return; | return; | ||||
parameter.hints = DISTRHO::PARAMETER_IS_AUTOMABLE; | parameter.hints = DISTRHO::PARAMETER_IS_AUTOMABLE; | ||||
@@ -166,14 +165,7 @@ protected: | |||||
parameter.ranges.min = 0.0f; | parameter.ranges.min = 0.0f; | ||||
parameter.ranges.max = 127.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: | case 0x01: | ||||
parameter.name = "0x01 Modulation"; | parameter.name = "0x01 Modulation"; | ||||
@@ -192,15 +184,18 @@ protected: | |||||
break; | break; | ||||
case 0x07: | case 0x07: | ||||
parameter.name = "0x07 Volume"; | parameter.name = "0x07 Volume"; | ||||
parameter.ranges.def = 100.0f; | |||||
break; | break; | ||||
case 0x08: | case 0x08: | ||||
parameter.name = "0x08 Balance"; | parameter.name = "0x08 Balance"; | ||||
parameter.ranges.def = 63.5f; | |||||
break; | break; | ||||
case 0x09: | case 0x09: | ||||
parameter.name = "0x09 (Undefined)"; | parameter.name = "0x09 (Undefined)"; | ||||
break; | break; | ||||
case 0x0A: | case 0x0A: | ||||
parameter.name = "0x0A Pan"; | parameter.name = "0x0A Pan"; | ||||
parameter.ranges.def = 63.5f; | |||||
break; | break; | ||||
case 0x0B: | case 0x0B: | ||||
parameter.name = "0x0B Expression"; | parameter.name = "0x0B Expression"; | ||||
@@ -325,6 +320,9 @@ protected: | |||||
case 0x5F: | case 0x5F: | ||||
parameter.name = "0x5F FX 5 Depth [Phaser]"; | parameter.name = "0x5F FX 5 Depth [Phaser]"; | ||||
break; | break; | ||||
default: | |||||
parameter.name = ""; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -343,39 +341,41 @@ protected: | |||||
float d_parameterValue(uint32_t index) override | float d_parameterValue(uint32_t index) override | ||||
{ | { | ||||
if (index >= paramCount) | |||||
if (index >= kParamCount) | |||||
return 0.0f; | return 0.0f; | ||||
return paramBuffers[index]; | |||||
return fParamBuffers[index]; | |||||
} | } | ||||
void d_setParameterValue(uint32_t index, float value) override | void d_setParameterValue(uint32_t index, float value) override | ||||
{ | { | ||||
if (index >= paramCount) | |||||
if (index >= kParamCount) | |||||
return; | return; | ||||
paramBuffers[index] = value; | |||||
fParamBuffers[index] = value; | |||||
} | } | ||||
void d_setProgram(uint32_t index) override | void d_setProgram(uint32_t index) override | ||||
{ | { | ||||
if (index >= programCount) | |||||
if (index >= kProgramCount) | |||||
return; | return; | ||||
if (kData->curPluginCount == 0) | |||||
if (kData->curPluginCount == 0 || kData->plugins == nullptr) | |||||
return; | 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 | 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) | for (unsigned int i=0; i < kData->curPluginCount; ++i) | ||||
{ | { | ||||
CarlaPlugin* const plugin(getPluginUnchecked(i)); | CarlaPlugin* const plugin(getPluginUnchecked(i)); | ||||
@@ -410,7 +399,7 @@ protected: | |||||
plugin->setActive(true, true, false); | plugin->setActive(true, true, false); | ||||
} | } | ||||
carla_copyFloat(prevParamBuffers, paramBuffers, paramCount); | |||||
carla_copyFloat(fPrevParamBuffers, fParamBuffers, kParamCount); | |||||
} | } | ||||
void d_deactivate() override | void d_deactivate() override | ||||
@@ -422,6 +411,9 @@ protected: | |||||
if (plugin != nullptr && plugin->enabled()) | if (plugin != nullptr && plugin->enabled()) | ||||
plugin->setActive(false, true, false); | 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 | 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(); | return proccessPendingEvents(); | ||||
} | } | ||||
// --------------------------------------------------------------- | |||||
// create audio buffers | // create audio buffers | ||||
float* inBuf[2] = { inputs[0], inputs[1] }; | float* inBuf[2] = { inputs[0], inputs[1] }; | ||||
float* outBuf[2] = { outputs[0], outputs[1] }; | float* outBuf[2] = { outputs[0], outputs[1] }; | ||||
// --------------------------------------------------------------- | |||||
// initialize input events | // initialize input events | ||||
carla_zeroStruct<EngineEvent>(kData->rack.in, RACK_EVENT_COUNT); | carla_zeroStruct<EngineEvent>(kData->rack.in, RACK_EVENT_COUNT); | ||||
{ | { | ||||
uint32_t engineEventIndex = 0; | 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; | 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) | 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; | 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); | processRack(inBuf, outBuf, frames); | ||||
proccessPendingEvents(); | |||||
} | } | ||||
// --------------------------------------------- | // --------------------------------------------- | ||||
@@ -512,8 +542,8 @@ protected: | |||||
// --------------------------------------------- | // --------------------------------------------- | ||||
private: | private: | ||||
float paramBuffers[paramCount]; | |||||
float prevParamBuffers[paramCount]; | |||||
float fParamBuffers[kParamCount]; | |||||
float fPrevParamBuffers[kParamCount]; | |||||
}; | }; | ||||
CARLA_BACKEND_END_NAMESPACE | CARLA_BACKEND_END_NAMESPACE | ||||
@@ -533,4 +563,4 @@ END_NAMESPACE_DISTRHO | |||||
#include "DistrhoPluginMain.cpp" | #include "DistrhoPluginMain.cpp" | ||||
#endif // CARLA_ENGINE_PLUGIN | |||||
#endif // WANT_PLUGIN |
@@ -350,7 +350,7 @@ public: | |||||
fMidiInEvents.clear(); | fMidiInEvents.clear(); | ||||
//fMidiOutEvents.clear(); | //fMidiOutEvents.clear(); | ||||
return true; | |||||
return (! hasError); | |||||
} | } | ||||
bool isRunning() const override | 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_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, SIGNAL("triggered()"), SLOT("slot_fileSave()")) | ||||
self.connect(self.ui.act_file_save_as, SIGNAL("triggered()"), SLOT("slot_fileSaveAs()")) | 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_start, SIGNAL("triggered()"), SLOT("slot_engineStart()")) | ||||
self.connect(self.ui.act_engine_stop, SIGNAL("triggered()"), SLOT("slot_engineStop()")) | 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) | filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter) | ||||
if filenameTry: | if filenameTry: | ||||
# FIXME - show dialog to user | |||||
# FIXME - show dialog to user (remove all plugins?) | |||||
self.removeAllPlugins() | self.removeAllPlugins() | ||||
self.loadProject(filenameTry) | self.loadProject(filenameTry) | ||||
@@ -1373,16 +1374,70 @@ class CarlaMainW(QMainWindow): | |||||
fileFilter = self.tr("Carla Project File (*.carxp)") | fileFilter = self.tr("Carla Project File (*.carxp)") | ||||
filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter) | 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() | @pyqtSlot() | ||||
def slot_fileSaveAs(self): | def slot_fileSaveAs(self): | ||||
self.slot_fileSave(True) | 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() | @pyqtSlot() | ||||
def slot_loadProjectLater(self): | def slot_loadProjectLater(self): | ||||
self.fProjectLoading = True | self.fProjectLoading = True | ||||