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