Carla: Implement bridge state restore

This commit is contained in:
falkTX 2012-12-26 00:30:27 +00:00
parent aeee5dac92
commit 7044f9288e
7 changed files with 794 additions and 25 deletions

1
.gitignore vendored
View File

@ -112,7 +112,6 @@ c++/carla-native/zynaddsubfx/UI/VirKeyboard.h
_/
all-recheck
c++/caitlyn/
c++/carla/
c++/carla-backend/carla-dynamic.lv2/
c++/carla-backend/carla_backend_lv2.cpp
c++/carla-backend/carla_backend_vst.cpp

View File

@ -20,10 +20,14 @@
#include "carla_bridge_client.hpp"
#include "carla_bridge_toolkit.hpp"
#include "carla_plugin.hpp"
#include "../carla/Shared.hpp"
#include <set>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QtCore/QThread>
#include <QtXml/QDomDocument>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
# include <QtWidgets/QApplication>
@ -221,12 +225,12 @@ public:
m_uiShow = showUI;
}
void guiClosedCallback();
protected:
QApplication* app;
CarlaBackend::CarlaPluginGUI* gui;
void guiClosedCallback();
private:
bool m_hasUI;
bool m_uiQuit;
@ -253,6 +257,10 @@ public:
msgTimerGUI = 0;
msgTimerOSC = 0;
needsResize = 0;
nextWidth = 0;
nextHeight = 0;
engine = nullptr;
plugin = nullptr;
}
@ -276,15 +284,12 @@ public:
if (! plugin)
return;
// create window if needed
bool guiResizable;
CarlaBackend::GuiType guiType;
plugin->getGuiInfo(&guiType, &guiResizable);
CarlaBridgeToolkitPlugin* const plugToolkit = (CarlaBridgeToolkitPlugin*)m_toolkit;
qWarning("----------------------------------------------------- trying..., %s", CarlaBackend::GuiType2Str(guiType));
if (guiType == CarlaBackend::GUI_INTERNAL_QT4 || guiType == CarlaBackend::GUI_INTERNAL_COCOA || guiType == CarlaBackend::GUI_INTERNAL_HWND || guiType == CarlaBackend::GUI_INTERNAL_X11)
{
plugin->setGuiContainer(plugToolkit->getContainer());
@ -332,10 +337,264 @@ public:
CARLA_ASSERT(plugin);
this->plugin = plugin;
// load carla plugin preset if possible
if (const char* const pName = plugin->name())
{
QFile pFile(QDir::currentPath() + QDir::separator() + pName + ".carxs");
qDebug("Trying to load plugin preset file '%s'", pFile.fileName().toUtf8().constData());
if (! /*(*/pFile.exists() /*&& pFile.isReadable())*/)
{
qDebug("Plugin preset file doesn't exist or is not readable");
return;
}
if (! pFile.open(QFile::ReadOnly))
{
qWarning("Plugin preset file read failed");
return;
}
QDomDocument xml;
xml.setContent(pFile.readAll());
QDomElement xmlNode(xml.documentElement());
if (xmlNode.tagName() == "CARLA-PRESET")
{
loadStateDict(getSaveStateDictFromXML(xmlNode));
}
else
qWarning("Plugin preset file is not valid or corrupted");
pFile.close();
}
}
// ---------------------------------------------------------------------
void loadStateDict(const CarlaSaveState* const content)
{
CARLA_ASSERT(content);
if (! content)
return;
qDebug("Loading plugin state now...");
// ---------------------------------------------------------------------
// Part 1 - set custom data (except chunks)
foreach (const CarlaStateCustomData& customData, content->customData)
{
if (customData.type != CUSTOM_DATA_CHUNK)
{
const char* const type = customData.type.toUtf8().constData();
const char* const key = customData.key.toUtf8().constData();
const char* const value = customData.value.toUtf8().constData();
plugin->setCustomData(type, key, value, true);
}
}
// ---------------------------------------------------------------------
// Part 2 - set program
int32_t programId = -1;
if (! content->currentProgramName.isEmpty())
{
const uint32_t programCount = plugin->programCount();
char strBuf[STR_MAX] = { 0 };
plugin->getProgramName(content->currentProgramIndex, strBuf);
QString testProgramName(strBuf);
// Program name matches
if (content->currentProgramName == testProgramName)
{
programId = content->currentProgramIndex;
}
// index < count
else if (content->currentProgramIndex < (int32_t)programCount)
{
programId = content->currentProgramIndex;
}
// index not valid, try to find by name
else
{
for (uint32_t i=0; i < programCount; i++)
{
plugin->getProgramName(i, strBuf);
testProgramName = QString(strBuf);
if (content->currentProgramName == testProgramName)
{
programId = i;
break;
}
}
}
}
// set program now, if valid
if (programId >= 0)
{
plugin->setProgram(programId, true, true, false, true);
}
// ---------------------------------------------------------------------
// Part 3 - set midi program
if (content->currentMidiBank >= 0 and content->currentMidiProgram >= 0)
{
const uint32_t midiProgramCount = plugin->midiProgramCount();
for (uint32_t i=0; i < midiProgramCount; i++)
{
const MidiProgramData* const midiProgramData = plugin->midiProgramData(i);
if ((int32_t)midiProgramData->bank == content->currentMidiBank && (int32_t)midiProgramData->program == content->currentMidiProgram)
{
plugin->setMidiProgram(i, true, true, false, true);
break;
}
}
}
// ---------------------------------------------------------------------
// Part 4a - get plugin parameter symbols
struct ParamSymbol {
uint32_t index;
QString symbol;
ParamSymbol() {}
ParamSymbol(uint32_t index_, const char* symbol_)
: index(index_),
symbol(symbol_) {}
};
QVector<ParamSymbol> paramSymbols;
foreach (const CarlaStateParameter& parameter, content->parameters)
{
if (! parameter.symbol.isEmpty())
{
char strBuf[STR_MAX] = { 0 };
plugin->getParameterSymbol(parameter.index, strBuf);
if (strBuf[0] != 0)
{
ParamSymbol ps(parameter.index, strBuf);
paramSymbols.append(ps);
}
}
}
// ---------------------------------------------------------------------
// Part 4b - set parameter values (carefully)
const double sampleRate = engine->getSampleRate();
foreach (const CarlaStateParameter& parameter, content->parameters)
{
int32_t index = -1;
if (content->type == "LADSPA")
{
// Try to set by symbol, otherwise use index
if (! parameter.symbol.isEmpty())
{
bool breaked = false;
foreach (const ParamSymbol& ps, paramSymbols)
{
if (parameter.symbol == ps.symbol)
{
index = ps.index;
breaked = true;
break;
}
}
if (! breaked)
index = parameter.index;
}
else
index = parameter.index;
}
else if (content->type == "LV2")
{
// Symbol only
if (! parameter.symbol.isEmpty())
{
bool breaked = false;
foreach (const ParamSymbol& ps, paramSymbols)
{
if (parameter.symbol == ps.symbol)
{
index = ps.index;
breaked = true;
break;
}
}
if (! breaked)
qWarning("Failed to find LV2 parameter symbol for '%s')", parameter.symbol.toUtf8().constData());
}
else
qWarning("LV2 Plugin parameter '%s' has no symbol", parameter.name.toUtf8().constData());
}
else
{
// Index only
index = parameter.index;
}
// Now set parameter
if (index >= 0)
{
const ParameterData* const paramData = plugin->parameterData(index);
double value = parameter.value;
if (paramData->hints & PARAMETER_USES_SAMPLERATE)
value *= sampleRate;
plugin->setParameterValue(index, value, true, true, false);
plugin->setParameterMidiCC(index, parameter.midiCC, true, false);
plugin->setParameterMidiChannel(index, parameter.midiChannel-1, true, false);
}
else
qWarning("Could not set parameter data for '%s')", parameter.name.toUtf8().constData());
}
// ---------------------------------------------------------------------
// Part 5 - set chunk data
foreach (const CarlaStateCustomData& customData, content->customData)
{
if (customData.type == CUSTOM_DATA_CHUNK)
{
const char* const type = customData.type.toUtf8().constData();
const char* const key = customData.key.toUtf8().constData();
const char* const value = customData.value.toUtf8().constData();
plugin->setCustomData(type, key, value, true);
}
}
if (! content->chunk.isEmpty())
plugin->setChunkData(content->chunk.toUtf8().constData());
qDebug("Loading plugin state now finished");
}
void guiClosed()
{
CARLA_ASSERT(engine);
@ -352,16 +611,6 @@ public:
plugin->showGui(yesNo);
}
// ---------------------------------------------------------------------
static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3, const char* const valueStr)
{
CARLA_ASSERT(ptr);
if (CarlaPluginClient* const _this_ = (CarlaPluginClient*)ptr)
_this_->handleCallback(action, value1, value2, value3, valueStr);
}
// ---------------------------------------------------------------------
// processing
@ -534,19 +783,119 @@ public:
}
// ---------------------------------------------------------------------
// callback
static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3, const char* const valueStr)
{
CARLA_ASSERT(ptr);
if (CarlaPluginClient* const _this_ = (CarlaPluginClient*)ptr)
_this_->handleCallback(action, value1, value2, value3, valueStr);
}
// ---------------------------------------------------------------------
protected:
int msgTimerGUI, msgTimerOSC;
int msgTimerGUI;
int msgTimerOSC;
bool needsResize;
int nextWidth;
int nextHeight;
CarlaBackend::CarlaEngine* engine;
CarlaBackend::CarlaPlugin* plugin;
std::set<int32_t> parametersToUpdate;
void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const double value3, const char* const valueStr)
{
qDebug("CarlaPluginClient::handleCallback(%s, %i, %i, %g \"%s\")", CarlaBackend::CallbackType2Str(action), value1, value2, value3, valueStr);
CARLA_BACKEND_USE_NAMESPACE
qDebug("CarlaPluginClient::handleCallback(%s, %i, %i, %g \"%s\")", CallbackType2Str(action), value1, value2, value3, valueStr);
if (! engine)
return;
switch (action)
{
case CALLBACK_DEBUG:
break;
case CALLBACK_PARAMETER_VALUE_CHANGED:
parametersToUpdate.insert(value1);
break;
case CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED:
// todo, unused?
break;
case CALLBACK_PARAMETER_MIDI_CC_CHANGED:
// todo, unused?
break;
case CALLBACK_PROGRAM_CHANGED:
engine->osc_send_bridge_set_program(value1);
break;
case CALLBACK_MIDI_PROGRAM_CHANGED:
engine->osc_send_bridge_set_midi_program(value1);
break;
case CALLBACK_NOTE_ON:
// todo
break;
case CALLBACK_NOTE_OFF:
// todo
break;
case CALLBACK_SHOW_GUI:
if (value1 == 0)
{
CarlaBridgeToolkitPlugin* const plugToolkit = (CarlaBridgeToolkitPlugin*)m_toolkit;
plugToolkit->guiClosedCallback();
}
break;
case CALLBACK_RESIZE_GUI:
nextWidth = value1;
nextHeight = value2;
needsResize = true;
break;
case CALLBACK_UPDATE:
// todo
break;
case CALLBACK_RELOAD_INFO:
// todo
break;
case CALLBACK_RELOAD_PARAMETERS:
// todo
break;
case CALLBACK_RELOAD_PROGRAMS:
// todo
break;
case CALLBACK_RELOAD_ALL:
// todo
break;
case CALLBACK_NSM_ANNOUNCE:
case CALLBACK_NSM_OPEN1:
case CALLBACK_NSM_OPEN2:
case CALLBACK_NSM_SAVE:
break;
case CALLBACK_ERROR:
break;
case CALLBACK_QUIT:
m_toolkit->quit();
break;
}
}
void timerEvent(QTimerEvent* const event)
@ -579,7 +928,6 @@ protected:
void CarlaBridgeToolkitPlugin::show()
{
qDebug("----------------------------------------------------------------------------------------------------------");
qDebug("CarlaBridgeToolkitPlugin::show()");
CARLA_ASSERT(gui);

View File

@ -1,9 +1,9 @@
# QtCreator project file
contains(QT_VERSION, ^5.*) {
QT = core widgets
QT = core widgets xml
} else {
QT = core gui
QT = core gui xml
}
CONFIG = debug link_pkgconfig qt warn_on
@ -25,6 +25,13 @@ HEADERS = \
../carla_bridge_osc.hpp \
../carla_bridge_toolkit.hpp \
# carla
SOURCES += \
../../carla/Shared.cpp
HEADERS += \
../../carla/Shared.hpp
# carla-engine
SOURCES += \
../../carla-engine/carla_engine.cpp \

257
c++/carla/Shared.cpp Normal file
View File

@ -0,0 +1,257 @@
/*
* Carla (frontend)
* Copyright (C) 2012 Filipe Coelho <falktx@falktx.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For a full copy of the GNU General Public License see the COPYING file
*/
#include "Shared.hpp"
//#define __STDC_LIMIT_MACROS
//#include <cstdint>
CARLA_BACKEND_USE_NAMESPACE
// ------------------------------------------------------------------------------------------------
// Carla Host object
CarlaHostObject Carla;
// ------------------------------------------------------------------------------------------------------------
// Carla XML helpers
const CarlaSaveState* getSaveStateDictFromXML(const QDomNode& xmlNode)
{
static CarlaSaveState saveState;
saveState.reset();
QDomNode node(xmlNode.firstChild());
while (! node.isNull())
{
// ------------------------------------------------------
// Info
if (node.toElement().tagName() == "Info")
{
QDomNode xmlInfo(node.toElement().firstChild());
while (! xmlInfo.isNull())
{
const QString tag = xmlInfo.toElement().tagName();
const QString text = xmlInfo.toElement().text(); //.strip();
if (tag == "Type")
saveState.type = text;
else if (tag == "Name")
saveState.name = xmlSafeString(text, false);
else if (tag == "Label" || tag == "URI")
saveState.label = xmlSafeString(text, false);
else if (tag == "Binary")
saveState.binary = xmlSafeString(text, false);
else if (tag == "UniqueID")
{
bool ok;
long uniqueID = text.toLong(&ok);
if (ok) saveState.uniqueID = uniqueID;
}
xmlInfo = xmlInfo.nextSibling();
}
}
// ------------------------------------------------------
// Data
else if (node.toElement().tagName() == "Data")
{
QDomNode xmlData(node.toElement().firstChild());
while (! xmlData.isNull())
{
const QString tag = xmlData.toElement().tagName();
const QString text = xmlData.toElement().text(); //.strip();
// ----------------------------------------------
// Internal Data
if (tag == "Active")
{
saveState.active = bool(text == "Yes");
}
else if (tag == "DryWet")
{
bool ok;
double value = text.toDouble(&ok);
if (ok) saveState.dryWet = value;
}
else if (tag == "Volume")
{
bool ok;
double value = text.toDouble(&ok);
if (ok) saveState.volume = value;
}
else if (tag == "Balance-Left")
{
bool ok;
double value = text.toDouble(&ok);
if (ok) saveState.balanceLeft = value;
}
else if (tag == "Balance-Right")
{
bool ok;
double value = text.toDouble(&ok);
if (ok) saveState.balanceRight = value;
}
// ----------------------------------------------
// Program (current)
else if (tag == "CurrentProgramIndex")
{
bool ok;
int value = text.toInt(&ok);
if (ok) saveState.currentProgramIndex = value;
}
else if (tag == "CurrentProgramName")
{
saveState.currentProgramName = xmlSafeString(text, false);
}
// ----------------------------------------------
// Midi Program (current)
else if (tag == "CurrentMidiBank")
{
bool ok;
int value = text.toInt(&ok);
if (ok) saveState.currentMidiBank = value;
}
else if (tag == "CurrentMidiProgram")
{
bool ok;
int value = text.toInt(&ok);
if (ok) saveState.currentMidiProgram = value;
}
// ----------------------------------------------
// Parameters
else if (tag == "Parameter")
{
CarlaStateParameter stateParameter;
QDomNode xmlSubData(xmlData.toElement().firstChild());
while (! xmlSubData.isNull())
{
const QString pTag = xmlSubData.toElement().tagName();
const QString pText = xmlSubData.toElement().text(); //.strip();
if (pTag == "index")
{
bool ok;
uint index = pText.toUInt(&ok);
if (ok) stateParameter.index = index;
}
else if (pTag == "name")
{
stateParameter.name = xmlSafeString(pText, false);
}
else if (pTag == "symbol")
{
stateParameter.symbol = xmlSafeString(pText, false);
}
else if (pTag == "value")
{
bool ok;
double value = pText.toDouble(&ok);
if (ok) stateParameter.value = value;
}
else if (pTag == "midiChannel")
{
bool ok;
uint channel = pText.toUInt(&ok);
if (ok && channel < 16)
stateParameter.midiChannel = channel;
}
else if (pTag == "midiCC")
{
bool ok;
int cc = pText.toInt(&ok);
if (ok && cc < INT16_MAX)
stateParameter.midiCC = cc;
}
xmlSubData = xmlSubData.nextSibling();
}
saveState.parameters.append(stateParameter);
}
// ----------------------------------------------
// Custom Data
else if (tag == "CustomData")
{
CarlaStateCustomData stateCustomData;
QDomNode xmlSubData(xmlData.toElement().firstChild());
while (! xmlSubData.isNull())
{
const QString cTag = xmlSubData.toElement().tagName();
const QString cText = xmlSubData.toElement().text(); //.strip();
if (cTag == "type")
stateCustomData.type = xmlSafeString(cText, false);
else if (cTag == "key")
stateCustomData.key = xmlSafeString(cText, false);
else if (cTag == "value")
stateCustomData.value = xmlSafeString(cText, false);
xmlSubData = xmlSubData.nextSibling();
}
saveState.customData.append(stateCustomData);
}
// ----------------------------------------------
// Chunk
else if (tag == "Chunk")
{
saveState.chunk = xmlSafeString(text, false);
}
// ----------------------------------------------
xmlData = xmlData.nextSibling();
}
}
// ------------------------------------------------------
node = node.nextSibling();
}
return &saveState;
}
QString xmlSafeString(QString string, const bool toXml)
{
if (toXml)
return string.replace("&", "&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
else
return string.replace("&amp;", "&").replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"");
}

151
c++/carla/Shared.hpp Normal file
View File

@ -0,0 +1,151 @@
/*
* Carla (frontend)
* Copyright (C) 2012 Filipe Coelho <falktx@falktx.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For a full copy of the GNU General Public License see the COPYING file
*/
#ifndef SHARED_HPP
#define SHARED_HPP
#include "carla_backend.hpp"
#include <QtCore/QString>
#include <QtCore/QVector>
#include <QtXml/QDomNode>
CARLA_BACKEND_USE_NAMESPACE
#define VERSION "0.5.0"
// ------------------------------------------------------------------------------------------------
// Carla Host object
struct CarlaHostObject {
void* host;
void* gui;
bool isControl;
ProcessMode processMode;
unsigned int maxParameters;
CarlaHostObject()
: host(nullptr),
gui(nullptr),
isControl(false),
processMode(PROCESS_MODE_CONTINUOUS_RACK),
maxParameters(MAX_PARAMETERS) {}
};
extern CarlaHostObject Carla;
// ------------------------------------------------------------------------------------------------
// Carla GUI stuff
#define ICON_STATE_NULL 0
#define ICON_STATE_WAIT 1
#define ICON_STATE_OFF 2
#define ICON_STATE_ON 3
#define PALETTE_COLOR_NONE 0
#define PALETTE_COLOR_WHITE 1
#define PALETTE_COLOR_RED 2
#define PALETTE_COLOR_GREEN 3
#define PALETTE_COLOR_BLUE 4
#define PALETTE_COLOR_YELLOW 5
#define PALETTE_COLOR_ORANGE 6
#define PALETTE_COLOR_BROWN 7
#define PALETTE_COLOR_PINK 8
struct CarlaStateParameter {
uint32_t index;
QString name;
QString symbol;
double value;
uint8_t midiChannel;
int16_t midiCC;
CarlaStateParameter()
: index(0),
value(0.0),
midiChannel(1),
midiCC(-1) {}
};
struct CarlaStateCustomData {
QString type;
QString key;
QString value;
CarlaStateCustomData() {}
};
struct CarlaSaveState {
QString type;
QString name;
QString label;
QString binary;
long uniqueID;
bool active;
double dryWet;
double volume;
double balanceLeft;
double balanceRight;
QVector<CarlaStateParameter> parameters;
int32_t currentProgramIndex;
QString currentProgramName;
int32_t currentMidiBank;
int32_t currentMidiProgram;
QVector<CarlaStateCustomData> customData;
QString chunk;
CarlaSaveState()
: uniqueID(0),
active(false),
dryWet(1.0),
volume(1.0),
balanceLeft(-1.0),
balanceRight(1.0),
currentProgramIndex(-1),
currentMidiBank(-1),
currentMidiProgram(-1) {}
void reset()
{
type.clear();
name.clear();
label.clear();
binary.clear();
uniqueID = 0;
active = false;
dryWet = 1.0;
volume = 1.0;
balanceLeft = -1.0;
balanceRight = 1.0;
parameters.clear();
currentProgramIndex = -1;
currentProgramName.clear();
currentMidiBank = -1;
currentMidiProgram = -1;
customData.clear();
chunk.clear();
}
};
// ------------------------------------------------------------------------------------------------------------
// Carla XML helpers
const CarlaSaveState* getSaveStateDictFromXML(const QDomNode& xmlNode);
QString xmlSafeString(QString string, const bool toXml);
#endif // SHARED_HPP

View File

@ -12,6 +12,10 @@ GUI:
-------------------
- APPS -
Cadence:
- add freq info to systray tooltip
- add freq Hz change
Claudia:
- Cleanup DB
- Handle sample-rate changes in JACK (made possible by switch-master)

View File

@ -267,6 +267,9 @@ CarlaSaveState = {
'Chunk': None
}
# ------------------------------------------------------------------------------------------------------------
# Carla XML helpers
def getSaveStateDictFromXML(xmlNode):
saveState = deepcopy(CarlaSaveState)
@ -378,7 +381,7 @@ def getSaveStateDictFromXML(xmlNode):
cText = xmlSubData.toElement().text().strip()
if cTag == "type":
stateCustomData['type'] = cText
stateCustomData['type'] = xmlSafeString(cText, False)
elif cTag == "key":
stateCustomData['key'] = xmlSafeString(cText, False)
elif cTag == "value":
@ -1751,7 +1754,7 @@ class PluginWidget(QFrame, ui_carla_plugin.Ui_PluginWidget):
# Part 1 - set custom data (except binary/chunks)
for customData in content['CustomData']:
if customData['type'] not in (CUSTOM_DATA_BINARY, CUSTOM_DATA_CHUNK):
if customData['type'] != CUSTOM_DATA_CHUNK:
Carla.host.set_custom_data(self.m_pluginId, customData['type'], customData['key'], customData['value'])
# ---------------------------------------------------------------------
@ -1763,7 +1766,7 @@ class PluginWidget(QFrame, ui_carla_plugin.Ui_PluginWidget):
programCount = Carla.host.get_program_count(self.m_pluginId)
testProgramName = cString(Carla.host.get_program_name(self.m_pluginId, content['CurrentProgramIndex']))
# Program index and name matches
# Program name matches
if content['CurrentProgramName'] == testProgramName:
programId = content['CurrentProgramIndex']
@ -1863,7 +1866,7 @@ class PluginWidget(QFrame, ui_carla_plugin.Ui_PluginWidget):
# Part 5 - set chunk data
for customData in content['CustomData']:
if customData['type'] in (CUSTOM_DATA_BINARY, CUSTOM_DATA_CHUNK):
if customData['type'] == CUSTOM_DATA_CHUNK:
Carla.host.set_custom_data(self.m_pluginId, customData['type'], customData['key'], customData['value'])
if content['Chunk']: