started windows vst support
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#include "vsthostwin.h"
|
||||
|
||||
// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#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 audioMasterGetCurrentProcessLevel:
|
||||
return 0;
|
||||
// Handle other opcodes here... there will be lots of them
|
||||
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() {
|
||||
plugin = NULL;
|
||||
|
||||
const char* vst_path = "C:\\Users\\Matt\\Downloads\\ToneGenerator_Win\\ToneGenerator_64b.dll";
|
||||
wchar_t win_char[200];
|
||||
mbstowcs(win_char, vst_path, 200);
|
||||
|
||||
HMODULE modulePtr = LoadLibrary(win_char);
|
||||
if(modulePtr == NULL) {
|
||||
DWORD dll_err = GetLastError();
|
||||
dout << "[ERROR] Failed to load VST" << vst_path << "-" << dll_err;
|
||||
if (dll_err == 193) dout << " Mixing 32-bit and 64-bit?";
|
||||
return;
|
||||
}
|
||||
|
||||
vstPluginFuncPtr mainEntryPoint =
|
||||
(vstPluginFuncPtr)GetProcAddress(modulePtr, "VSTPluginMain");
|
||||
// Instantiate the plugin
|
||||
plugin = mainEntryPoint(hostCallback);
|
||||
}
|
||||
|
||||
int 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) {
|
||||
printf("Plugin's magic number is bad\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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 plugin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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::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.
|
||||
silenceChannel(outputs, CHANNEL_COUNT, numFrames);
|
||||
|
||||
plugin->processReplacing(plugin, inputs, outputs, numFrames);
|
||||
}
|
||||
|
||||
void VSTHostWin::silenceChannel(float **channelData, int numChannels, long numFrames) {
|
||||
for(int channel = 0; channel < numChannels; ++channel) {
|
||||
for(long frame = 0; frame < numFrames; ++frame) {
|
||||
channelData[channel][frame] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VSTHostWin::VSTHostWin(Clip* c, const EffectMeta *em) : Effect(c, em) {
|
||||
EffectRow* interface_row = add_row("Open Interface");
|
||||
QPushButton* show_interface_btn = new QPushButton();
|
||||
interface_row->add_widget(show_interface_btn);
|
||||
loadPlugin();
|
||||
if (plugin != NULL) {
|
||||
initializeIO();
|
||||
configurePluginCallbacks();
|
||||
startPlugin();
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) {
|
||||
if (plugin != NULL) {
|
||||
// convert to floats
|
||||
int nb_frames = qMin(nb_bytes >> 2, BLOCK_SIZE);
|
||||
int lim = qMin(nb_bytes, BLOCK_SIZE << 2);
|
||||
for (int i=0;i<lim;i+=4) {
|
||||
qint16 left_sample = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
|
||||
qint16 right_sample = (qint16) (((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF));
|
||||
|
||||
int index = i >> 2;
|
||||
inputs[0][index] = float(left_sample) / float(INT16_MAX);
|
||||
inputs[1][index] = float(right_sample) / float(INT16_MAX);
|
||||
}
|
||||
|
||||
// send to VST
|
||||
processAudio(nb_frames);
|
||||
|
||||
// convert back to int16
|
||||
for (int i=0;i<nb_frames;i++) {
|
||||
qint16 left_sample = qRound(inputs[0][i]*INT16_MAX);
|
||||
qint16 right_sample = qRound(inputs[1][i]*INT16_MAX);
|
||||
|
||||
int index = i << 2;
|
||||
|
||||
samples[index+3] = (quint8) (right_sample >> 8);
|
||||
samples[index+2] = (quint8) right_sample;
|
||||
samples[index+1] = (quint8) (left_sample >> 8);
|
||||
samples[index] = (quint8) left_sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VSTHostWin::show_interface() {
|
||||
// dispatcher(plugin, effEditOpen, 0, 1, NULL, 0.0f);
|
||||
// dispatcher(plugin, effEditOpen, 0, 0, mainWindow->winId());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#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 VSTHostWin : public Effect {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VSTHostWin(Clip* c, const EffectMeta* em);
|
||||
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
|
||||
private slots:
|
||||
void show_interface();
|
||||
private:
|
||||
void loadPlugin();
|
||||
dispatcherFuncPtr dispatcher;
|
||||
AEffect* plugin;
|
||||
int configurePluginCallbacks();
|
||||
void startPlugin();
|
||||
void resumePlugin();
|
||||
void suspendPlugin();
|
||||
bool canPluginDo(char *canDoString);
|
||||
void initializeIO();
|
||||
void processAudio(long numFrames);
|
||||
void silenceChannel(float **channelData, int numChannels, long numFrames);
|
||||
float** inputs;
|
||||
float** outputs;
|
||||
};
|
||||
|
||||
#endif // VSTHOSTWIN_H
|
||||
@@ -117,7 +117,8 @@ SOURCES += \
|
||||
ui/clickablelabel.cpp \
|
||||
project/keyframe.cpp \
|
||||
ui/rectangleselect.cpp \
|
||||
dialogs/actionsearch.cpp
|
||||
dialogs/actionsearch.cpp \
|
||||
effects/internal/vsthostwin.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
@@ -204,7 +205,8 @@ HEADERS += \
|
||||
ui/clickablelabel.h \
|
||||
project/keyframe.h \
|
||||
ui/rectangleselect.h \
|
||||
dialogs/actionsearch.h
|
||||
dialogs/actionsearch.h \
|
||||
effects/internal/vsthostwin.h
|
||||
|
||||
FORMS +=
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "effects/internal/paneffect.h"
|
||||
#include "effects/internal/shakeeffect.h"
|
||||
#include "effects/internal/cornerpineffect.h"
|
||||
#include "effects/internal/vsthostwin.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
@@ -60,6 +61,7 @@ Effect* create_effect(Clip* c, const EffectMeta* em) {
|
||||
case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em);
|
||||
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
|
||||
case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em);
|
||||
case EFFECT_INTERNAL_VST: return new VSTHostWin(c, em);
|
||||
}
|
||||
} else {
|
||||
dout << "[ERROR] Invalid effect data";
|
||||
@@ -92,6 +94,10 @@ void load_internal_effects() {
|
||||
em.internal = EFFECT_INTERNAL_PAN;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "VST Plugin 2.x";
|
||||
em.internal = EFFECT_INTERNAL_VST;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Tone";
|
||||
em.internal = EFFECT_INTERNAL_TONE;
|
||||
effects.append(em);
|
||||
@@ -466,6 +472,7 @@ void Effect::copy_field_keyframes(Effect* e) {
|
||||
EffectField* field = row->field(j);
|
||||
EffectField* copy_field = copy_row->field(j);
|
||||
copy_field->keyframes = field->keyframes;
|
||||
copy_field->set_current_data(field->get_current_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ extern QMutex effects_loaded;
|
||||
#define EFFECT_INTERNAL_SHAKE 7
|
||||
#define EFFECT_INTERNAL_TIMECODE 8
|
||||
#define EFFECT_INTERNAL_MASK 9
|
||||
|
||||
#define EFFECT_INTERNAL_VST 10
|
||||
|
||||
#define EFFECT_INTERNAL_CORNERPIN 12
|
||||
#define EFFECT_INTERNAL_COUNT 13
|
||||
|
||||
@@ -29,6 +29,8 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt
|
||||
|
||||
ui->addWidget(label, row, 0);
|
||||
|
||||
column_count = 1;
|
||||
|
||||
if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) {
|
||||
connect(label, SIGNAL(clicked()), this, SLOT(focus_row()));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
EffectRow(Effect* parent, bool save, QGridLayout* uilayout, const QString& n, int row);
|
||||
~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
|
||||
|
||||
Reference in New Issue
Block a user