Merge pull request #282 from olive-editor/winvst
vst 2.4 support on windows
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
#include "vsthostwin.h"
|
||||
|
||||
// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "playback/audio.h"
|
||||
#include "mainwindow.h"
|
||||
#include "debug.h"
|
||||
|
||||
#define BLOCK_SIZE 512
|
||||
#define CHANNEL_COUNT 2
|
||||
|
||||
// C callbacks
|
||||
extern "C" {
|
||||
// Main host callback
|
||||
VstIntPtr VSTCALLBACK hostCallback(AEffect *effect, int opcode, int index, long long value, void *ptr, float opt) {
|
||||
switch(opcode) {
|
||||
case audioMasterVersion:
|
||||
return 2400;
|
||||
case audioMasterIdle:
|
||||
effect->dispatcher(effect, effEditIdle, 0, 0, 0, 0);
|
||||
break;
|
||||
case 6: // audioMasterWantMidi
|
||||
return 0;
|
||||
case audioMasterGetCurrentProcessLevel:
|
||||
return 0;
|
||||
// Handle other opcodes here... there will be lots of them
|
||||
case audioMasterEndEdit: // change made
|
||||
mainWindow->setWindowModified(true);
|
||||
break;
|
||||
default:
|
||||
dout << "[INFO] Plugin requested unhandled opcode" << opcode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin's entry point
|
||||
typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host);
|
||||
// Plugin's getParameter() method
|
||||
typedef float (*getParameterFuncPtr)(AEffect *effect, VstInt32 index);
|
||||
// Plugin's setParameter() method
|
||||
typedef void (*setParameterFuncPtr)(AEffect *effect, VstInt32 index, float value);
|
||||
// Plugin's processEvents() method
|
||||
typedef VstInt32 (*processEventsFuncPtr)(VstEvents *events);
|
||||
// Plugin's process() method
|
||||
typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames);
|
||||
|
||||
void VSTHostWin::loadPlugin() {
|
||||
QString dll_fn = file_field->get_filename(0, true);
|
||||
LPCWSTR dll_fn_w = reinterpret_cast<const wchar_t*>(dll_fn.utf16());
|
||||
|
||||
modulePtr = LoadLibrary(dll_fn_w);
|
||||
if(modulePtr == NULL) {
|
||||
DWORD dll_err = GetLastError();
|
||||
dout << "[ERROR] Failed to load VST" << dll_fn_w << "-" << dll_err;
|
||||
QString msg_err = "Failed to load VST plugin \"" + dll_fn + "\": " + QString::number(dll_err);
|
||||
if (dll_err == 193) {
|
||||
#ifdef _WIN64
|
||||
msg_err += "\n\nNOTE: You can't load 32-bit VST plugins into a 64-bit build of Olive. Please find a 64-bit version of this plugin or switch to a 32-bit build of Olive.";
|
||||
#elif _WIN32
|
||||
msg_err += "\n\nNOTE: You can't load 64-bit VST plugins into a 32-bit build of Olive. Please find a 32-bit version of this plugin or switch to a 64-bit build of Olive.";
|
||||
#endif
|
||||
}
|
||||
QMessageBox::critical(mainWindow, "Error loading VST plugin", msg_err);
|
||||
return;
|
||||
}
|
||||
|
||||
vstPluginFuncPtr mainEntryPoint =
|
||||
(vstPluginFuncPtr)GetProcAddress(modulePtr, "VSTPluginMain");
|
||||
// Instantiate the plugin
|
||||
plugin = mainEntryPoint(hostCallback);
|
||||
}
|
||||
|
||||
void VSTHostWin::freePlugin() {
|
||||
if (plugin != NULL) {
|
||||
stopPlugin();
|
||||
FreeLibrary(modulePtr);
|
||||
plugin = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool VSTHostWin::configurePluginCallbacks() {
|
||||
// Check plugin's magic number
|
||||
// If incorrect, then the file either was not loaded properly, is not a
|
||||
// real VST plugin, or is otherwise corrupt.
|
||||
if(plugin->magic != kEffectMagic) {
|
||||
dout << "[ERROR] Plugin's magic number is bad";
|
||||
QMessageBox::critical(mainWindow, "VST Error", "Plugin's magic number is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create dispatcher handle
|
||||
dispatcher = (dispatcherFuncPtr)(plugin->dispatcher);
|
||||
|
||||
// Set up plugin callback functions
|
||||
plugin->getParameter = (getParameterFuncPtr)plugin->getParameter;
|
||||
plugin->processReplacing = (processFuncPtr)plugin->processReplacing;
|
||||
plugin->setParameter = (setParameterFuncPtr)plugin->setParameter;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VSTHostWin::startPlugin() {
|
||||
dispatcher(plugin, effOpen, 0, 0, NULL, 0.0f);
|
||||
|
||||
// Set some default properties
|
||||
dispatcher(plugin, effSetSampleRate, 0, 0, NULL, current_audio_freq());
|
||||
dispatcher(plugin, effSetBlockSize, 0, BLOCK_SIZE, NULL, 0.0f);
|
||||
|
||||
resumePlugin();
|
||||
}
|
||||
|
||||
void VSTHostWin::stopPlugin() {
|
||||
suspendPlugin();
|
||||
|
||||
dispatcher(plugin, effClose, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
void VSTHostWin::resumePlugin() {
|
||||
dispatcher(plugin, effMainsChanged, 0, 1, NULL, 0.0f);
|
||||
}
|
||||
|
||||
void VSTHostWin::suspendPlugin() {
|
||||
dispatcher(plugin, effMainsChanged, 0, 0, NULL, 0.0f);
|
||||
}
|
||||
|
||||
bool VSTHostWin::canPluginDo(char *canDoString) {
|
||||
return (dispatcher(plugin, effCanDo, 0, 0, (void*)canDoString, 0.0f) > 0);
|
||||
}
|
||||
|
||||
void VSTHostWin::initializeIO() {
|
||||
// inputs and outputs are assumed to be float** and are declared elsewhere,
|
||||
// most likely the are fields owned by this class. numChannels and blocksize
|
||||
// are also fields, both should be size_t (or unsigned int, if you prefer).
|
||||
inputs = new float* [CHANNEL_COUNT];
|
||||
outputs = new float* [CHANNEL_COUNT];
|
||||
for(int channel = 0; channel < CHANNEL_COUNT; channel++) {
|
||||
inputs[channel] = new float[BLOCK_SIZE];
|
||||
outputs[channel] = new float[BLOCK_SIZE];
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::processAudio(long numFrames) {
|
||||
// Always reset the output array before processing.
|
||||
for (int i=0;i<CHANNEL_COUNT;i++) {
|
||||
memset(outputs[i], 0, BLOCK_SIZE*sizeof(float));
|
||||
}
|
||||
|
||||
plugin->processReplacing(plugin, inputs, outputs, numFrames);
|
||||
}
|
||||
|
||||
VSTHostWin::VSTHostWin(Clip* c, const EffectMeta *em) : Effect(c, em) {
|
||||
plugin = NULL;
|
||||
|
||||
initializeIO();
|
||||
|
||||
file_field = add_row("Plugin", true, false)->add_field(EFFECT_FIELD_FILE, "filename");
|
||||
connect(file_field, SIGNAL(changed()), this, SLOT(change_plugin()));
|
||||
|
||||
EffectRow* interface_row = add_row("Interface", false, false);
|
||||
show_interface_btn = new QPushButton("Show");
|
||||
show_interface_btn->setCheckable(true);
|
||||
show_interface_btn->setEnabled(false);
|
||||
connect(show_interface_btn, SIGNAL(toggled(bool)), this, SLOT(show_interface(bool)));
|
||||
interface_row->add_widget(show_interface_btn);
|
||||
|
||||
dialog = new QDialog(mainWindow);
|
||||
dialog->setWindowTitle("VST Plugin");
|
||||
dialog->setAttribute(Qt::WA_NativeWindow, true);
|
||||
dialog->setWindowFlags(dialog->windowFlags() | Qt::MSWindowsFixedSizeDialogHint);
|
||||
connect(dialog, SIGNAL(finished(int)), this, SLOT(uncheck_show_button()));
|
||||
}
|
||||
|
||||
VSTHostWin::~VSTHostWin() {
|
||||
freePlugin();
|
||||
}
|
||||
|
||||
void VSTHostWin::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) {
|
||||
if (plugin != NULL) {
|
||||
int interval = BLOCK_SIZE*4;
|
||||
for (int i=0;i<nb_bytes;i+=interval) {
|
||||
int process_size = qMin(interval, nb_bytes - i);
|
||||
int lim = i + process_size;
|
||||
|
||||
// convert to float
|
||||
for (int j=i;j<lim;j+=4) {
|
||||
qint16 left_sample = (qint16) (((samples[j+1] & 0xFF) << 8) | (samples[j] & 0xFF));
|
||||
qint16 right_sample = (qint16) (((samples[j+3] & 0xFF) << 8) | (samples[j+2] & 0xFF));
|
||||
|
||||
int index = (j-i)>>2;
|
||||
inputs[0][index] = float(left_sample) / float(INT16_MAX);
|
||||
inputs[1][index] = float(right_sample) / float(INT16_MAX);
|
||||
}
|
||||
|
||||
// send to VST
|
||||
processAudio(process_size>>2);
|
||||
|
||||
// convert back to int16
|
||||
for (int j=i;j<lim;j+=4) {
|
||||
int index = (j-i)>>2;
|
||||
|
||||
qint16 left_sample = qRound(outputs[0][index] * INT16_MAX);
|
||||
qint16 right_sample = qRound(outputs[1][index] * INT16_MAX);
|
||||
|
||||
samples[j+3] = (quint8) (right_sample >> 8);
|
||||
samples[j+2] = (quint8) right_sample;
|
||||
samples[j+1] = (quint8) (left_sample >> 8);
|
||||
samples[j] = (quint8) left_sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::custom_load(QXmlStreamReader &stream) {
|
||||
if (stream.name() == "plugindata") {
|
||||
stream.readNext();
|
||||
QByteArray b = QByteArray::fromBase64(stream.text().toUtf8());
|
||||
const char* data = b.constData();
|
||||
if (plugin != NULL) {
|
||||
dispatcher(plugin, effSetChunk, 0, (VstInt32) b.size(), (void*) b.constData(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::save(QXmlStreamWriter &stream) {
|
||||
Effect::save(stream);
|
||||
if (plugin != NULL) {
|
||||
char* p = NULL;
|
||||
VstInt32 length = dispatcher(plugin, effGetChunk, 0, 0, &p, 0);
|
||||
QByteArray b(p, length);
|
||||
stream.writeTextElement("plugindata", b.toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::show_interface(bool show) {
|
||||
dialog->setVisible(show);
|
||||
}
|
||||
|
||||
void VSTHostWin::uncheck_show_button() {
|
||||
show_interface_btn->setChecked(false);
|
||||
}
|
||||
|
||||
void VSTHostWin::change_plugin() {
|
||||
freePlugin();
|
||||
loadPlugin();
|
||||
if (plugin != NULL) {
|
||||
if (configurePluginCallbacks()) {
|
||||
startPlugin();
|
||||
dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast<HWND>(dialog->winId()), 0);
|
||||
ERect* eRect = NULL;
|
||||
plugin->dispatcher(plugin, effEditGetRect, 0, 0, &eRect, 0);
|
||||
dialog->setFixedWidth(eRect->right);
|
||||
dialog->setFixedHeight(eRect->bottom);
|
||||
} else {
|
||||
FreeLibrary(modulePtr);
|
||||
plugin = NULL;
|
||||
}
|
||||
}
|
||||
show_interface_btn->setEnabled(plugin != NULL);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef VSTHOSTWIN_H
|
||||
#define VSTHOSTWIN_H
|
||||
|
||||
#include "project/effect.h"
|
||||
|
||||
#include <vst/aeffectx.h>
|
||||
|
||||
// Plugin's dispatcher function
|
||||
typedef VstIntPtr (*dispatcherFuncPtr)(AEffect *effect, VstInt32 opCode, VstInt32 index, VstInt32 value, void *ptr, float opt);
|
||||
|
||||
struct AEffect;
|
||||
class QDialog;
|
||||
|
||||
class VSTHostWin : public Effect {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VSTHostWin(Clip* c, const EffectMeta* em);
|
||||
~VSTHostWin();
|
||||
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
|
||||
|
||||
void custom_load(QXmlStreamReader& stream);
|
||||
void save(QXmlStreamWriter& stream);
|
||||
private slots:
|
||||
void show_interface(bool show);
|
||||
void uncheck_show_button();
|
||||
void change_plugin();
|
||||
private:
|
||||
EffectField* file_field;
|
||||
|
||||
void loadPlugin();
|
||||
void freePlugin();
|
||||
dispatcherFuncPtr dispatcher;
|
||||
AEffect* plugin;
|
||||
bool configurePluginCallbacks();
|
||||
void startPlugin();
|
||||
void stopPlugin();
|
||||
void resumePlugin();
|
||||
void suspendPlugin();
|
||||
bool canPluginDo(char *canDoString);
|
||||
void initializeIO();
|
||||
void processAudio(long numFrames);
|
||||
float** inputs;
|
||||
float** outputs;
|
||||
QDialog* dialog;
|
||||
QPushButton* show_interface_btn;
|
||||
HMODULE modulePtr;
|
||||
};
|
||||
|
||||
#endif // VSTHOSTWIN_H
|
||||
@@ -118,6 +118,7 @@ SOURCES += \
|
||||
project/keyframe.cpp \
|
||||
ui/rectangleselect.cpp \
|
||||
dialogs/actionsearch.cpp \
|
||||
ui/embeddedfilechooser.cpp \
|
||||
effects/internal/fillleftrighteffect.cpp
|
||||
|
||||
HEADERS += \
|
||||
@@ -206,13 +207,17 @@ HEADERS += \
|
||||
project/keyframe.h \
|
||||
ui/rectangleselect.h \
|
||||
dialogs/actionsearch.h \
|
||||
ui/embeddedfilechooser.h \
|
||||
effects/internal/fillleftrighteffect.h
|
||||
|
||||
FORMS +=
|
||||
|
||||
win32 {
|
||||
RC_FILE = packaging/windows/resources.rc
|
||||
LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32
|
||||
LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -luser32
|
||||
|
||||
SOURCES += effects/internal/vsthostwin.cpp
|
||||
HEADERS += effects/internal/vsthostwin.h
|
||||
}
|
||||
|
||||
mac {
|
||||
|
||||
+37
-65
@@ -28,6 +28,9 @@
|
||||
#include "effects/internal/paneffect.h"
|
||||
#include "effects/internal/shakeeffect.h"
|
||||
#include "effects/internal/cornerpineffect.h"
|
||||
#ifdef _WIN32
|
||||
#include "effects/internal/vsthostwin.h"
|
||||
#endif
|
||||
#include "effects/internal/fillleftrighteffect.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
@@ -62,6 +65,9 @@ Effect* create_effect(Clip* c, const EffectMeta* em) {
|
||||
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
|
||||
case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em);
|
||||
case EFFECT_INTERNAL_FILLLEFTRIGHT: return new FillLeftRightEffect(c, em);
|
||||
#ifdef _WIN32
|
||||
case EFFECT_INTERNAL_VST: return new VSTHostWin(c, em);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
dout << "[ERROR] Invalid effect data";
|
||||
@@ -94,6 +100,12 @@ void load_internal_effects() {
|
||||
em.internal = EFFECT_INTERNAL_PAN;
|
||||
effects.append(em);
|
||||
|
||||
#ifdef _WIN32
|
||||
em.name = "VST Plugin 2.x";
|
||||
em.internal = EFFECT_INTERNAL_VST;
|
||||
effects.append(em);
|
||||
#endif
|
||||
|
||||
em.name = "Tone";
|
||||
em.internal = EFFECT_INTERNAL_TONE;
|
||||
effects.append(em);
|
||||
@@ -313,6 +325,8 @@ Effect::Effect(Clip* c, const EffectMeta *em) :
|
||||
type = EFFECT_FIELD_FONT;
|
||||
} else if (comp == "STRING") {
|
||||
type = EFFECT_FIELD_STRING;
|
||||
} else if (comp == "FILE") {
|
||||
type = EFFECT_FIELD_FILE;
|
||||
}
|
||||
} else if (attr.name() == "id") {
|
||||
id = attr.value().toString();
|
||||
@@ -405,6 +419,14 @@ Effect::Effect(Clip* c, const EffectMeta *em) :
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EFFECT_FIELD_FILE:
|
||||
for (int i=0;i<attributes.size();i++) {
|
||||
const QXmlStreamAttribute& attr = attributes.at(i);
|
||||
if (attr.name() == "filename") {
|
||||
field->set_filename(attr.value().toString());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +499,7 @@ void Effect::copy_field_keyframes(Effect* e) {
|
||||
}
|
||||
}
|
||||
|
||||
EffectRow* Effect::add_row(const QString& name, bool savable) {
|
||||
EffectRow* Effect::add_row(const QString& name, bool savable, bool keyframable) {
|
||||
EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size());
|
||||
rows.append(row);
|
||||
return row;
|
||||
@@ -586,10 +608,12 @@ QVariant load_data_from_string(int type, const QString& string) {
|
||||
switch (type) {
|
||||
case EFFECT_FIELD_DOUBLE: return string.toDouble();
|
||||
case EFFECT_FIELD_COLOR: return QColor(string);
|
||||
case EFFECT_FIELD_STRING: return string;
|
||||
case EFFECT_FIELD_BOOL: return (string == "1");
|
||||
case EFFECT_FIELD_COMBO: return string.toInt();
|
||||
case EFFECT_FIELD_FONT: return string;
|
||||
case EFFECT_FIELD_STRING:
|
||||
case EFFECT_FIELD_FONT:
|
||||
case EFFECT_FIELD_FILE:
|
||||
return string;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
@@ -598,10 +622,12 @@ QString save_data_to_string(int type, const QVariant& data) {
|
||||
switch (type) {
|
||||
case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble());
|
||||
case EFFECT_FIELD_COLOR: return data.value<QColor>().name();
|
||||
case EFFECT_FIELD_STRING: return data.toString();
|
||||
case EFFECT_FIELD_BOOL: return QString::number(data.toBool());
|
||||
case EFFECT_FIELD_COMBO: return QString::number(data.toInt());
|
||||
case EFFECT_FIELD_FONT: return data.toString();
|
||||
case EFFECT_FIELD_STRING:
|
||||
case EFFECT_FIELD_FONT:
|
||||
case EFFECT_FIELD_FILE:
|
||||
return data.toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -621,42 +647,6 @@ void Effect::load(QXmlStreamReader& stream) {
|
||||
while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) {
|
||||
stream.readNext();
|
||||
|
||||
// read keyframes
|
||||
/*if (stream.name() == "keyframes" && stream.isStartElement()) {
|
||||
for (int k=0;k<stream.attributes().size();k++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(k);
|
||||
if (attr.name() == "enabled") {
|
||||
row->setKeyframing(attr.value() == "1");
|
||||
}
|
||||
}
|
||||
if (row->isKeyframing()) {
|
||||
stream.readNext();
|
||||
while (!stream.atEnd() && !(stream.name() == "keyframes" && stream.isEndElement())) {
|
||||
if (stream.name() == "key" && stream.isStartElement()) {
|
||||
long keyframe_frame;
|
||||
int keyframe_type;
|
||||
for (int k=0;k<stream.attributes().size();k++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(k);
|
||||
if (attr.name() == "frame") {
|
||||
keyframe_frame = attr.value().toLong();
|
||||
} else if (attr.name() == "type") {
|
||||
keyframe_type = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
for (int k=0;k<row->fieldCount();k++) {
|
||||
EffectField* field = row->field(k);
|
||||
EffectKeyframe key;
|
||||
key.time = keyframe_frame;
|
||||
key.type = keyframe_type;
|
||||
field->keyframes.append(key);
|
||||
}
|
||||
}
|
||||
stream.readNext();
|
||||
}
|
||||
}
|
||||
stream.readNext();
|
||||
}*/
|
||||
|
||||
// read field
|
||||
if (stream.name() == "field" && stream.isStartElement()) {
|
||||
if (field_count < row->fieldCount()) {
|
||||
@@ -678,9 +668,6 @@ void Effect::load(QXmlStreamReader& stream) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO DEPRECATED, only used for backwards compatibility with 180820
|
||||
if (!found_field_by_id) dout << "[INFO] Found field by field number";
|
||||
|
||||
EffectField* field = row->field(field_number);
|
||||
|
||||
// get current field value
|
||||
@@ -732,10 +719,14 @@ void Effect::load(QXmlStreamReader& stream) {
|
||||
dout << "[ERROR] Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")";
|
||||
}
|
||||
row_count++;
|
||||
} else if (stream.isStartElement()) {
|
||||
custom_load(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Effect::custom_load(QXmlStreamReader &stream) {}
|
||||
|
||||
void Effect::save(QXmlStreamWriter& stream) {
|
||||
stream.writeAttribute("name", meta->name);
|
||||
stream.writeAttribute("enabled", QString::number(is_enabled()));
|
||||
@@ -885,6 +876,7 @@ void Effect::process_shader(double timecode, GLTextureCoords&) {
|
||||
glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_combo_index(timecode));
|
||||
break;
|
||||
case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value?
|
||||
case EFFECT_FIELD_FILE: break; // can you even send a string to a uniform value?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -920,27 +912,7 @@ GLuint Effect::process_superimpose(double timecode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//void Effect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count) {
|
||||
void Effect::process_audio(double, double, quint8*, int, int) {
|
||||
// only volume/pan, hand off to AU and VST for all other cases
|
||||
|
||||
/*double interval = (timecode_end-timecode_start)/nb_bytes;
|
||||
|
||||
for (int i=0;i<nb_bytes;i+=2) {
|
||||
qint32 samp = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
|
||||
|
||||
jsEngine.globalObject().setProperty("sample", samp);
|
||||
jsEngine.globalObject().setProperty("volume", row(0)->field(0)->get_double_value(timecode_start+(interval*i), true));
|
||||
QJSValue result = eval.call();
|
||||
samp = result.toInt();
|
||||
QJSValueList args;
|
||||
args << samples << nb_bytes;
|
||||
|
||||
|
||||
samples[i+1] = (quint8) (samp >> 8);
|
||||
samples[i] = (quint8) samp;
|
||||
}*/
|
||||
}
|
||||
void Effect::process_audio(double, double, quint8*, int, int) {}
|
||||
|
||||
void Effect::gizmo_draw(double, GLTextureCoords &) {}
|
||||
|
||||
|
||||
+4
-3
@@ -64,7 +64,7 @@ extern QMutex effects_loaded;
|
||||
#define EFFECT_INTERNAL_TIMECODE 8
|
||||
#define EFFECT_INTERNAL_MASK 9
|
||||
#define EFFECT_INTERNAL_FILLLEFTRIGHT 10
|
||||
|
||||
#define EFFECT_INTERNAL_VST 11
|
||||
#define EFFECT_INTERNAL_CORNERPIN 12
|
||||
#define EFFECT_INTERNAL_COUNT 13
|
||||
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
QString name;
|
||||
CollapsibleWidget* container;
|
||||
|
||||
EffectRow* add_row(const QString &name, bool savable = true);
|
||||
EffectRow* add_row(const QString &name, bool savable = true, bool keyframable = true);
|
||||
EffectRow* row(int i);
|
||||
int row_count();
|
||||
|
||||
@@ -136,7 +136,8 @@ public:
|
||||
void copy_field_keyframes(Effect *e);
|
||||
|
||||
void load(QXmlStreamReader& stream);
|
||||
void save(QXmlStreamWriter& stream);
|
||||
virtual void custom_load(QXmlStreamReader& stream);
|
||||
virtual void save(QXmlStreamWriter& stream);
|
||||
|
||||
// glsl handling
|
||||
bool is_open();
|
||||
|
||||
+33
-4
@@ -6,6 +6,7 @@
|
||||
#include "ui/checkboxex.h"
|
||||
#include "ui/comboboxex.h"
|
||||
#include "ui/fontcombobox.h"
|
||||
#include "ui/embeddedfilechooser.h"
|
||||
|
||||
#include "effectrow.h"
|
||||
#include "effect.h"
|
||||
@@ -70,6 +71,13 @@ EffectField::EffectField(EffectRow *parent, int t, const QString &i) :
|
||||
connect(fcb, SIGNAL(activated(int)), this, SLOT(ui_element_change()));
|
||||
}
|
||||
break;
|
||||
case EFFECT_FIELD_FILE:
|
||||
{
|
||||
EmbeddedFileChooser* efc = new EmbeddedFileChooser();
|
||||
ui_element = efc;
|
||||
connect(efc, SIGNAL(changed()), this, SLOT(ui_element_change()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +89,7 @@ QVariant EffectField::get_previous_data() {
|
||||
case EFFECT_FIELD_BOOL: return !static_cast<QCheckBox*>(ui_element)->isChecked();
|
||||
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->getPreviousIndex();
|
||||
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->getPreviousValue();
|
||||
case EFFECT_FIELD_FILE: return static_cast<EmbeddedFileChooser*>(ui_element)->getPreviousValue();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
@@ -93,6 +102,7 @@ QVariant EffectField::get_current_data() {
|
||||
case EFFECT_FIELD_BOOL: return static_cast<QCheckBox*>(ui_element)->isChecked();
|
||||
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->currentIndex();
|
||||
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->currentText();
|
||||
case EFFECT_FIELD_FILE: return static_cast<EmbeddedFileChooser*>(ui_element)->getFilename();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
@@ -113,6 +123,7 @@ void EffectField::set_current_data(const QVariant& data) {
|
||||
case EFFECT_FIELD_BOOL: return static_cast<QCheckBox*>(ui_element)->setChecked(data.toBool());
|
||||
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->setCurrentIndexEx(data.toInt());
|
||||
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->setCurrentTextEx(data.toString());
|
||||
case EFFECT_FIELD_FILE: return static_cast<EmbeddedFileChooser*>(ui_element)->setFilename(data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +257,12 @@ QVariant EffectField::validate_keyframe_data(double timecode, bool async) {
|
||||
}
|
||||
static_cast<FontCombobox*>(ui_element)->setCurrentTextEx(before_data.toString());
|
||||
break;
|
||||
case EFFECT_FIELD_FILE:
|
||||
if (async) {
|
||||
return before_data;
|
||||
}
|
||||
static_cast<EmbeddedFileChooser*>(ui_element)->setFilename(before_data.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
@@ -313,12 +330,12 @@ int EffectField::get_combo_index(double timecode, bool async) {
|
||||
return static_cast<ComboBoxEx*>(ui_element)->currentIndex();
|
||||
}
|
||||
|
||||
const QVariant EffectField::get_combo_data(double timecode) {
|
||||
QVariant EffectField::get_combo_data(double timecode) {
|
||||
validate_keyframe_data(timecode);
|
||||
return static_cast<ComboBoxEx*>(ui_element)->currentData();
|
||||
}
|
||||
|
||||
const QString EffectField::get_combo_string(double timecode) {
|
||||
QString EffectField::get_combo_string(double timecode) {
|
||||
validate_keyframe_data(timecode);
|
||||
return static_cast<ComboBoxEx*>(ui_element)->currentText();
|
||||
}
|
||||
@@ -343,7 +360,7 @@ void EffectField::set_bool_value(bool b) {
|
||||
return static_cast<QCheckBox*>(ui_element)->setChecked(b);
|
||||
}
|
||||
|
||||
const QString EffectField::get_string_value(double timecode, bool async) {
|
||||
QString EffectField::get_string_value(double timecode, bool async) {
|
||||
if (async && hasKeyframes()) {
|
||||
return validate_keyframe_data(timecode, true).toString();
|
||||
}
|
||||
@@ -355,7 +372,7 @@ void EffectField::set_string_value(const QString& s) {
|
||||
static_cast<TextEditEx*>(ui_element)->setPlainTextEx(s);
|
||||
}
|
||||
|
||||
const QString EffectField::get_font_name(double timecode, bool async) {
|
||||
QString EffectField::get_font_name(double timecode, bool async) {
|
||||
if (async && hasKeyframes()) {
|
||||
return validate_keyframe_data(timecode, true).toString();
|
||||
}
|
||||
@@ -378,3 +395,15 @@ QColor EffectField::get_color_value(double timecode, bool async) {
|
||||
void EffectField::set_color_value(QColor color) {
|
||||
static_cast<ColorButton*>(ui_element)->set_color(color);
|
||||
}
|
||||
|
||||
QString EffectField::get_filename(double timecode, bool async) {
|
||||
if (async && hasKeyframes()) {
|
||||
return validate_keyframe_data(timecode, true).toString();
|
||||
}
|
||||
validate_keyframe_data(timecode);
|
||||
return static_cast<EmbeddedFileChooser*>(ui_element)->getFilename();
|
||||
}
|
||||
|
||||
void EffectField::set_filename(const QString &s) {
|
||||
static_cast<EmbeddedFileChooser*>(ui_element)->setFilename(s);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define EFFECT_FIELD_BOOL 3
|
||||
#define EFFECT_FIELD_COMBO 4
|
||||
#define EFFECT_FIELD_FONT 5
|
||||
#define EFFECT_FIELD_FILE 6
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
@@ -39,25 +40,28 @@ public:
|
||||
void set_double_minimum_value(double v);
|
||||
void set_double_maximum_value(double v);
|
||||
|
||||
const QString get_string_value(double timecode, bool async = false);
|
||||
QString get_string_value(double timecode, bool async = false);
|
||||
void set_string_value(const QString &s);
|
||||
|
||||
void add_combo_item(const QString& name, const QVariant &data);
|
||||
int get_combo_index(double timecode, bool async = false);
|
||||
const QVariant get_combo_data(double timecode);
|
||||
const QString get_combo_string(double timecode);
|
||||
QVariant get_combo_data(double timecode);
|
||||
QString get_combo_string(double timecode);
|
||||
void set_combo_index(int index);
|
||||
void set_combo_string(const QString& s);
|
||||
|
||||
bool get_bool_value(double timecode, bool async = false);
|
||||
void set_bool_value(bool b);
|
||||
|
||||
const QString get_font_name(double timecode, bool async = false);
|
||||
QString get_font_name(double timecode, bool async = false);
|
||||
void set_font_name(const QString& s);
|
||||
|
||||
QColor get_color_value(double timecode, bool async = false);
|
||||
void set_color_value(QColor color);
|
||||
|
||||
QString get_filename(double timecode, bool async = false);
|
||||
void set_filename(const QString& s);
|
||||
|
||||
QWidget* get_ui_element();
|
||||
void set_enabled(bool e);
|
||||
QVector<EffectKeyframe> keyframes;
|
||||
|
||||
+11
-3
@@ -16,7 +16,7 @@
|
||||
#include "ui/keyframenavigator.h"
|
||||
#include "ui/clickablelabel.h"
|
||||
|
||||
EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QString &n, int row) :
|
||||
EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QString &n, int row, bool keyframable) :
|
||||
parent_effect(parent),
|
||||
savable(save),
|
||||
keyframing(false),
|
||||
@@ -29,7 +29,9 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt
|
||||
|
||||
ui->addWidget(label, row, 0);
|
||||
|
||||
if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) {
|
||||
column_count = 1;
|
||||
|
||||
if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION && keyframable) {
|
||||
connect(label, SIGNAL(clicked()), this, SLOT(focus_row()));
|
||||
|
||||
keyframe_nav = new KeyframeNavigator();
|
||||
@@ -145,11 +147,17 @@ EffectField* EffectRow::add_field(int type, const QString& id, int colspan) {
|
||||
if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) connect(field, SIGNAL(clicked()), this, SLOT(focus_row()));
|
||||
fields.append(field);
|
||||
QWidget* element = field->get_ui_element();
|
||||
ui->addWidget(element, ui_row, fields.size(), 1, colspan);
|
||||
ui->addWidget(element, ui_row, column_count, 1, colspan);
|
||||
column_count++;
|
||||
connect(field, SIGNAL(changed()), parent_effect, SLOT(field_changed()));
|
||||
return field;
|
||||
}
|
||||
|
||||
void EffectRow::add_widget(QWidget* w) {
|
||||
ui->addWidget(w, ui_row, column_count);
|
||||
column_count++;
|
||||
}
|
||||
|
||||
EffectRow::~EffectRow() {
|
||||
for (int i=0;i<fields.size();i++) {
|
||||
delete fields.at(i);
|
||||
|
||||
+3
-1
@@ -17,9 +17,10 @@ class ClickableLabel;
|
||||
class EffectRow : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EffectRow(Effect* parent, bool save, QGridLayout* uilayout, const QString& n, int row);
|
||||
EffectRow(Effect* parent, bool save, QGridLayout* uilayout, const QString& n, int row, bool keyframable = true);
|
||||
~EffectRow();
|
||||
EffectField* add_field(int type, const QString &id, int colspan = 1);
|
||||
void add_widget(QWidget *w);
|
||||
EffectField* field(int i);
|
||||
int fieldCount();
|
||||
void set_keyframe_now(ComboAction *ca);
|
||||
@@ -52,6 +53,7 @@ private:
|
||||
QVector<QVariant> unsafe_old_data;
|
||||
QVector<bool> key_is_new;
|
||||
|
||||
int column_count;
|
||||
};
|
||||
|
||||
#endif // EFFECTROW_H
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "embeddedfilechooser.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFileInfo>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
|
||||
EmbeddedFileChooser::EmbeddedFileChooser(QWidget* parent) : QWidget(parent) {
|
||||
QHBoxLayout* layout = new QHBoxLayout();
|
||||
layout->setMargin(0);
|
||||
setLayout(layout);
|
||||
file_label = new QLabel();
|
||||
update_label();
|
||||
layout->addWidget(file_label);
|
||||
QPushButton* browse_button = new QPushButton("...");
|
||||
browse_button->setFixedWidth(25);
|
||||
layout->addWidget(browse_button);
|
||||
connect(browse_button, SIGNAL(clicked(bool)), this, SLOT(browse()));
|
||||
}
|
||||
|
||||
const QString &EmbeddedFileChooser::getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
const QString &EmbeddedFileChooser::getPreviousValue() {
|
||||
return old_filename;
|
||||
}
|
||||
|
||||
void EmbeddedFileChooser::setFilename(const QString &s) {
|
||||
old_filename = filename;
|
||||
filename = s;
|
||||
update_label();
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void EmbeddedFileChooser::update_label() {
|
||||
QString l = "<html>File: ";
|
||||
if (filename.isEmpty()) {
|
||||
l += "(none)";
|
||||
} else {
|
||||
bool file_exists = QFileInfo::exists(filename);
|
||||
if (!file_exists) l += "<font color='red'>";
|
||||
QString short_fn = filename.mid(filename.lastIndexOf('/')+1);
|
||||
if (short_fn.size() > 20) {
|
||||
l += "..." + short_fn.right(20);
|
||||
} else {
|
||||
l += short_fn;
|
||||
}
|
||||
if (!file_exists) l += "</font>";
|
||||
}
|
||||
l += "</html>";
|
||||
file_label->setText(l);
|
||||
}
|
||||
|
||||
void EmbeddedFileChooser::browse() {
|
||||
QString fn = QFileDialog::getOpenFileName(this);
|
||||
if (!fn.isEmpty()) {
|
||||
setFilename(fn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef EMBEDDEDFILECHOOSER_H
|
||||
#define EMBEDDEDFILECHOOSER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
|
||||
class EmbeddedFileChooser : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EmbeddedFileChooser(QWidget* parent = 0);
|
||||
|
||||
const QString& getFilename();
|
||||
const QString& getPreviousValue();
|
||||
void setFilename(const QString& s);
|
||||
signals:
|
||||
void changed();
|
||||
private:
|
||||
QLabel* file_label;
|
||||
QString filename;
|
||||
QString old_filename;
|
||||
void update_label();
|
||||
private slots:
|
||||
void browse();
|
||||
};
|
||||
|
||||
#endif // EMBEDDEDFILECHOOSER_H
|
||||
Reference in New Issue
Block a user