VSTe VSTGUI inline UI Editor を使わないで、Viewに出力音声波形を描画する方法
概要
VSTGUI inline UI Editorの使い方って、あまり情報がないので、よくわからなかったのですが、とりあえず、使わない方法はかなり情報があったので、音声波形を描画することができました。
ViewContainerというコントロールには、CViewクラスを継承したコンテキストを追加するクラスがあるので、簡単そうに思えたのでやってみたらできちゃったので、公開しておこうと思います。各コントロールの画像は自分で用意して下さい。
ファイル構成は以下のとおりです。
- fuid.h
- def.h
- GlobalConstants.h
- factory.cpp
- resource.rc
- proc.h
- proc.cpp
- view.h
- view.cpp
- guieditor.h
- guieditor.cpp
- fuid.h
#pragma once
#include "pluginterfaces\base\funknown.h"
#include "def.h"
namespace Steinberg{
namespace Vst {
// ============================================================================================
// FUID
// ============================================================================================
static const FUID ProcessorUID (0x6169B858, 0x954548D0, 0xBE846909, 0x7F12CEE0);
static const FUID ControllerUID(0x994440DB, 0x57DA4F70, 0xAF20CD49, 0xBE7C4EB5);
} }
- def.h
#pragma once
#ifndef __DEF_H__
#define __DEF_H__
#include <math.h>
// ============================================================================================
// PARAM TAG
// ============================================================================================
#define PARAM_DEPTH_TAG 100
#define PARAM_SPEED_TAG 101
#define PARAM_TYPE_TAG 102
#define PARAM_VIEW_TAG 103
#endif
- GlobalConstants.h
#pragma once
#include "guieditor.h"
#ifndef __CxxGUIEdit_Instance__
#define __CxxGUIEdit_Instance__
extern Steinberg::Vst::CxxGUIEdit* CxxGUIEdit_Instance;
#endif
- factory.cpp
#include "public.sdk/source/main/pluginfactory.h"
#include "fuid.h"
#include "proc.h"
#include "view.h"
// ============================================================================================
// 固有情報1
// ============================================================================================
#define MYVST_VENDOR "CxxVST"
#define MYVST_URL "https://wiki.yo-net.jp/"
#define MYVST_EMAIL "mailto:info_@yo-net.jp"
// ============================================================================================
// 固有情報2
// ============================================================================================
#define MYVST_VSTNAME "CxxVST ViewGUI"
#define MYVST_VERSION "0"
#define MYVST_SUBCATEGORIES Vst::PlugType::kFx
//bool DeinitModule() { return true; }
// ============================================================================================
// 固有情報マクロ定義
// ============================================================================================
BEGIN_FACTORY_DEF(MYVST_VENDOR, MYVST_URL, MYVST_EMAIL)
DEF_CLASS2(INLINE_UID_FROM_FUID(Steinberg::Vst::ProcessorUID),
PClassInfo::kManyInstances,
kVstAudioEffectClass,
MYVST_VSTNAME,
Vst::kDistributable,
MYVST_SUBCATEGORIES,
MYVST_VERSION,
kVstVersionString,
Steinberg::Vst::CxxProc::createInstance )
DEF_CLASS2(INLINE_UID_FROM_FUID(Steinberg::Vst::ControllerUID),
PClassInfo::kManyInstances,
kVstComponentControllerClass,
MYVST_VSTNAME " Controller",
0,
"",
MYVST_VERSION,
kVstVersionString,
Steinberg::Vst::CxxEdit::createInstance )
END_FACTORY
- resource.rc
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
background.png PNG ".\\images\\background.png"
background2.png PNG ".\\images\\background2.png"
knob.png PNG ".\\images\\knob.png"
knob_handle.png PNG ".\\images\\knob_handle.png"
slider.png PNG ".\\images\\slider.png"
slider_handle.png PNG ".\\images\\slider_handle.png"
- proc.h
#pragma once
#ifndef __PROC_H__
#define __PROC_H__
#include "public.sdk/source/vst/vstaudioeffect.h"
#include "pluginterfaces/vst/ivstparameterchanges.h"
#include "vstgui/lib/cview.h"
#include "def.h"
#include "guieditor.h"
#include <functional>
namespace Steinberg{
namespace Vst {
// ============================================================================================
// Procクラス
// ============================================================================================
class CxxProc : public AudioEffect
{
protected:
ParamValue depth;
ParamValue freq;
int32 type;
ParamValue theta;
public:
CxxProc();
~CxxProc();
tresult PLUGIN_API initialize(FUnknown* context);
tresult PLUGIN_API setBusArrangements(SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts);
tresult PLUGIN_API process(ProcessData& data);
static FUnknown* createInstance(void*) { return (IAudioProcessor*)new CxxProc(); }
private:
Sample32** outL3Times;
Sample32** outR3Times;
int bufferSize;
bool initialized;
int TimesCount;
};
} }
#endif
- proc.cpp
#include "def.h"
#include "fuid.h"
#include "proc.h"
namespace Steinberg{
namespace Vst {
CxxProc::CxxProc()
{
setControllerClass(ControllerUID);
outL3Times = nullptr;
outR3Times = nullptr;
bufferSize = 0;
initialized = false;
TimesCount = 0;
}
tresult PLUGIN_API CxxProc::initialize(FUnknown* context)
{
tresult result = AudioEffect::initialize(context);
if (result == kResultTrue)
{
addAudioInput(STR16("AudioInput"), SpeakerArr::kStereo);
addAudioOutput(STR16("AudioOutput"), SpeakerArr::kStereo);
addEventInput(STR16("Event Input"), 1);
depth = 1.0f;
freq = 5.0f;
type = 0;
theta = 0.0f;
}
return result;
}
tresult PLUGIN_API CxxProc::setBusArrangements(SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts)
{
if (numIns == 1 && numOuts == 1 && inputs[0] == SpeakerArr::kStereo && outputs[0] == SpeakerArr::kStereo)
{
return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts);
}
return kResultFalse;
}
CxxProc::~CxxProc() {
//3回分のdata.NumSamplesの数だけ履歴として保存しているので
//3回分の動的配列を消去
if (outL3Times != nullptr)
{
for (int i = 0; i < 3; ++i)
{
delete[] outL3Times[i];
}
delete[] outL3Times;
}
if (outR3Times != nullptr)
{
for (int i = 0; i < 3; ++i)
{
delete[] outR3Times[i];
}
delete[] outR3Times;
}
}
tresult PLUGIN_API CxxProc::process(ProcessData& data)
{
if (data.inputParameterChanges != NULL)
{
int32 paramChangeCount = data.inputParameterChanges->getParameterCount();
for (int32 i = 0; i < paramChangeCount; i++)
{
IParamValueQueue* queue = data.inputParameterChanges->getParameterData(i);
if (queue != NULL)
{
int32 tag = queue->getParameterId();
int32 valueChangeCount = queue->getPointCount();
ParamValue value;
int32 sampleOffset;
if (queue->getPoint(valueChangeCount - 1, sampleOffset, value) == kResultTrue)
{
switch (tag)
{
case PARAM_DEPTH_TAG:
depth = value;
break;
case PARAM_SPEED_TAG:
freq = (29.5f * value) + 0.5f;
break;
case PARAM_TYPE_TAG:
type = (int32)(value * 2.0f);
break;
}
}
}
}
}
Sample32* inL = data.inputs[0].channelBuffers32[0];
Sample32* inR = data.inputs[0].channelBuffers32[1];
Sample32* outL = data.outputs[0].channelBuffers32[0];
Sample32* outR = data.outputs[0].channelBuffers32[1];
#if DEVELOPMENT // 必ず DEVELOPMENT で囲むこと。
// FDebugPrint("data.numSamples:%5d\n", data.numSamples);
#endif
for (int32 i = 0; i < data.numSamples; i++)
{
Sample32 a = (sin(theta) * 0.5f) + 0.5f;
Sample32 b = (1.0f - depth) + (a * depth);
Sample32 c = (1.0f - depth) + ((1.0f - a) * depth);
switch (type)
{
case 0:
outL[i] = depth * inL[i];
outR[i] = depth * inR[i];
break;
case 1:
outL[i] = (a * depth) * inL[i];
outR[i] = (a * depth) * inR[i];
break;
case 2:
outL[i] = b * inL[i];
outR[i] = c * inR[i];
break;
}
theta += (2.0f * 3.14159265f * freq) / 44100.0f;
}
if (!initialized)//初回
{
if (data.numSamples <= 0)
{
return kResultFalse;
}
bufferSize = data.numSamples;
//3回分のdata.NumSamplesの数だけ履歴として保存しているので
//3回分の動的配列を生成 履歴数も可変にできるよう変更するとViewの尺度が長時間になり蜜な描画になる。
//初期化されていないときは、[履歴数][data.NumSamples]の配列を生成する。
outL3Times = new Sample32 * [3];
outR3Times = new Sample32 * [3];
for (int i = 0; i < 3; ++i)
{
outL3Times[i] = new Sample32[bufferSize];
outR3Times[i] = new Sample32[bufferSize];
}
initialized = true;
}
else//2回目
{
if (data.numSamples != bufferSize)//サンプル数が変わったら、履歴用の配列もサイズを再調整。
{
bufferSize = data.numSamples;
Sample32** newOutL3Times = new Sample32 * [3];
Sample32** newOutR3Times = new Sample32 * [3];
for (int i = 0; i < 3; ++i)
{
newOutL3Times[i] = new Sample32[bufferSize];
newOutR3Times[i] = new Sample32[bufferSize];
}
for (int i = 0; i < 3; ++i)//古い履歴バッファ配列を消滅
{
delete[] outL3Times[i];
delete[] outR3Times[i];
}
delete[] outL3Times;
delete[] outR3Times;
outL3Times = newOutL3Times;//新しく生成したバッファ配列にポインタを設定
outR3Times = newOutR3Times;
}
}
Sample32* temp;
switch(TimesCount){
case 0:
case 2:
case 3:
case 6:
case 7:
temp = outL3Times[2];//一時的に生成したポインタを一番古い履歴から取得。
outL3Times[2] = outL3Times[1];//一番古いポインタを真ん中のデータ履歴から取得
outL3Times[1] = outL3Times[0];//真ん中のポインタを一番新しい履歴から取得
outL3Times[0] = temp;//一番新しいポインタを一時的に生成したポインタから取得
temp = outR3Times[2];
outR3Times[2] = outR3Times[1];
outR3Times[1] = outR3Times[0];
outR3Times[0] = temp;
for (int i = 0; i < bufferSize; ++i)
{
outL3Times[0][i] = outL[i];//一番新しいポインタに出力データを取得。
outR3Times[0][i] = outR[i];
}
#if DEVELOPMENT // 必ず DEVELOPMENT で囲むこと。
//FDebugPrint("outL3Times1:%5f, %5f, %5f, %5f\n", outL3Times[0][1], outL3Times[0][2], outL3Times[0][3], outL3Times[0][4]);
//FDebugPrint("outL3Times2:%5f, %5f, %5f, %5f\n", outL3Times[1][1], outL3Times[1][2], outL3Times[1][3], outL3Times[1][4]);
//FDebugPrint("outL3Times3:%5f, %5f, %5f, %5f\n\n", outL3Times[2][1], outL3Times[2][2], outL3Times[2][3], outL3Times[2][4]);
#endif
break;
default:
break;
}
TimesCount++;
if (TimesCount >= 10) {
TimesCount = 0;//履歴カウンタが10を超えたら0に 0, [1], 2, 3, [4], [5], 6, 7, [8], [9]
//0,2,3,6,7だけ更新。
}
if(CxxGUIEdit_Instance != nullptr){
CxxGUIEdit_Instance->setSingnal(outL3Times, outR3Times, data.numSamples);
}
return kResultTrue;
}
} }
- view.h
#pragma once
#ifndef __CONTROLLER_H__
#define __CONTROLLER_H__
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "def.h"
#include "guieditor.h"
namespace Steinberg{
namespace Vst {
class CxxEdit : public EditController
{
public:
tresult PLUGIN_API initialize(FUnknown* context);
IPlugView* PLUGIN_API createView(const char* name);
static FUnknown* createInstance(void*) { return (IEditController*)new CxxEdit(); }
};
} }
#endif
- view.cpp
#include "def.h"
#include "fuid.h"
#include "view.h"
namespace Steinberg{
namespace Vst {
tresult PLUGIN_API CxxEdit::initialize(FUnknown* context)
{
tresult result = EditController::initialize(context);
if (result == kResultTrue)
{
parameters.addParameter(STR16("Depth"), STR16("..."), 0, 1, ParameterInfo::kCanAutomate, PARAM_DEPTH_TAG);
RangeParameter* param1 = new RangeParameter(STR16("Speed"), PARAM_SPEED_TAG, STR16("Hz"), 0.5f, 30.0f, 5.0f);
param1->setPrecision(2);
parameters.addParameter(param1);
StringListParameter* param2 = new StringListParameter(STR16("Type"), PARAM_TYPE_TAG);
param2->appendString(STR16("Volume"));
param2->appendString(STR16("Tremolo"));
param2->appendString(STR16("Panning"));
parameters.addParameter(param2);
}
result = kResultTrue;
return result;
}
IPlugView* PLUGIN_API CxxEdit::createView(const char* name)
{
if (strcmp(name, "editor") == 0)
{
CxxGUIEdit* view = new CxxGUIEdit(this);
return view;
}
return 0;
}
} }
- guieditor.h
#pragma once
#ifndef __GUIEDITOR_H__
#define __GUIEDITOR_H__
#include "public.sdk/source/vst/vstguieditor.h"
#include "pluginterfaces/vst/ivstplugview.h"
#include "def.h"
#include "proc.h"
namespace Steinberg {
namespace Vst {
using namespace VSTGUI;
class CWaveDrawView : public CView
{
protected:
CDrawContext::LineList lines;
public:
CWaveDrawView(const CRect& size);
virtual void draw(CDrawContext* pContext) override;
void setWave(float* buf[], int size);
};
class CxxGUIEdit : public VSTGUIEditor, public IControlListener
{
CWaveDrawView* waveView;
public:
void setSingnal(Sample32** pSample32inL, Sample32** pSample32inR, int32 int32NumSamples);
CxxGUIEdit(void* controller);
virtual bool PLUGIN_API open(void* parent, const PlatformType& platformType = PlatformType::kDefaultNative);
virtual void PLUGIN_API close();
void valueChanged(CControl* pControl);
void createKnob(ParamID tag, int x, int y);
void createSlider(ParamID tag, int x, int y);
void createCombobox(ParamID tag, int x, int y);
CViewContainer* createView(ParamID tag, int x, int y);
DELEGATE_REFCOUNT(VSTGUIEditor)
CMessageResult notify(CBaseObject* sender, const char* message) override;
private:
Sample32** pSample32inL_Trans;
Sample32** pSample32inR_Trans;
int32 int32NumSamples_Trans;
};
} }
#include "GlobalConstants.h"
#endif
- guieditor.cpp
#include "guieditor.h"
Steinberg::Vst::CxxGUIEdit* CxxGUIEdit_Instance = nullptr;
namespace Steinberg {
namespace Vst {
CxxGUIEdit::CxxGUIEdit(void* controller)
: VSTGUIEditor(controller)
{
ViewRect viewRect(0, 0, 800, 200);
setRect(viewRect);
CxxGUIEdit_Instance = this;
}
// ============================================================================================
// GUIの起動初期設定
// ============================================================================================
bool PLUGIN_API CxxGUIEdit::open(void* parent, const PlatformType& platformType)
{
if (frame) { return false; }//frameは継承元のVSTGUIEditorのウィンドウハンドル。
CRect size(0, 0, 800, 200);
frame = new CFrame(size, this);
if (frame == NULL) { return false; }
CBitmap* cbmp = new CBitmap("background.png");
//大きさ横幅800[px]x高さ200[px]の白系背景
frame->setBackground(cbmp);
cbmp->forget();
frame->open(parent);
//初期値用の適当な波形
waveView = new CWaveDrawView(size);
float* wav[3];
wav[0] = new float[256];
wav[1] = new float[256];
wav[2] = new float[256];
float fTheta = 0;
for (int i = 0; i < 256; i++)
{
wav[0][i] = sin(fTheta + (2.0 * 3.14159265 * (double)(i * 880) / 44100.0));
fTheta += 2.0 * 3.14159265 * (double)(i * 880) / 44100.0
wav[1][i] = sin(fTheta + (2.0 * 3.14159265 * (double)(i * 880) / 44100.0));
fTheta += 2.0 * 3.14159265 * (double)(i * 880) / 44100.0
wav[2][i] = sin(fTheta + (2.0 * 3.14159265 * (double)(i * 880) / 44100.0));
fTheta += 2.0 * 3.14159265 * (double)(i * 880) / 44100.0
}
waveView->setWave(wav, 256);
createCombobox(PARAM_TYPE_TAG, 10, 10);
createKnob(PARAM_SPEED_TAG, 20, 35);
createSlider(PARAM_DEPTH_TAG, 20, 90);
CViewContainer* control = createView(PARAM_VIEW_TAG, 80, 10);
control->addView(waveView);//CViewContainerControlにCView型の波形描画を追加
return true;
}
void PLUGIN_API CxxGUIEdit::close()
{
if (frame)
{
frame->forget();
frame = 0;
}
}
void CxxGUIEdit::valueChanged(CControl* pControl)
{
int32 index = pControl->getTag();
float value = pControl->getValueNormalized();
controller->setParamNormalized(index, value);
controller->performEdit(index, value);
}
CViewContainer* CxxGUIEdit::createView(ParamID tag, int x, int y)
{
CBitmap* backbmp = new CBitmap("background2.png");
//大きさ横幅700[px]x高さ190[px]の黒系背景
CRect size(0, 0, backbmp->getWidth(), backbmp->getHeight());
size.offset(x, y);
CRect& size2 = size;//参照型変数に代入
CViewContainer* CViewContainerControl = new CViewContainer(size2);
CViewContainerControl->setBackground(backbmp);
frame->addView(CViewContainerControl);//親ウィンドウにViewContainerコントロールを追加。
backbmp->forget();
return CViewContainerControl;
}
void CxxGUIEdit::createKnob(ParamID tag, int x, int y)
{
CBitmap *backbmp = new CBitmap("knob.png");
CBitmap *handlebmp = new CBitmap("knob_handle.png");
CRect size(0, 0, backbmp->getWidth(), backbmp->getHeight());
size.offset(x, y);
CKnob* control = new CKnob(size, this, tag, backbmp, handlebmp);
ParamValue value = controller->getParamNormalized(tag);
control->setValueNormalized(value);
frame->addView(control);
backbmp->forget();
handlebmp->forget();
}
void CxxGUIEdit::createSlider(ParamID tag, int x, int y)
{
CBitmap *backbmp = new CBitmap("slider.png");
CBitmap *handlebmp = new CBitmap("slider_handle.png");
CRect size;
size(0, 0, backbmp->getWidth(), backbmp->getHeight());
size.offset(x, y);
int bmpmargin = 1;
CVerticalSlider* control = new CVerticalSlider(size, this, tag,
y + bmpmargin,
y + backbmp->getHeight() - (handlebmp->getHeight() + bmpmargin),
handlebmp, backbmp);
ParamValue value = controller->getParamNormalized(tag);
control->setValueNormalized(value);
frame->addView(control);
backbmp->forget();
handlebmp->forget();
}
void CxxGUIEdit::createCombobox(ParamID tag, int x, int y)
{
CRect size;
size(0, 0, 60, 16);
size.offset(x, y);
COptionMenu* control = new COptionMenu(size, this, tag);
control->addEntry("Volume");
control->addEntry("Tremolo");
control->addEntry("Panning");
ParamValue value = controller->getParamNormalized(tag);
control->setValueNormalized(value);
control->setFont(kNormalFontSmaller);
control->setFontColor(kWhiteCColor);
control->setBackColor(kBlackCColor);
control->setFrameColor(kRedCColor);
frame->addView(control);
}
CWaveDrawView::CWaveDrawView(const CRect& size)
: CView(size)
{
};
void CWaveDrawView::draw(CDrawContext* pContext)
{
if (getDrawBackground())
{
getDrawBackground()->draw(pContext, getViewSize());
}
pContext->setFrameColor(kBlueCColor);
pContext->setLineStyle(kLineSolid);
pContext->setLineWidth(4.0);
pContext->drawLines(lines);
setDirty(false);
}
void CWaveDrawView::setWave(float* buf[], int size)
{
lines.clear();
CRect viewSize = getViewSize();
CCoord leftX = viewSize.left;
CCoord centerY = viewSize.getCenter().y;
CPoint from(leftX, centerY);
for (int j = 0; j < 3; j++){
for (int i = 0; i < size; i++)
{
CCoord width = viewSize.getWidth();
CCoord height = viewSize.getHeight();
CPoint to(leftX + width * ((double)(j * size + i + 1) / (double)size * 3), centerY - height / 2.0 * buf[j][i]);
#if DEVELOPMENT // 必ず DEVELOPMENT で囲むこと。
// FDebugPrint("data.numSamples:%5f, %5f\n", leftX + width * ((double)(j * size + i + 1) / (double)size * 3), centerY - height / 2.0 * buf[j][i]);
#endif
CDrawContext::LinePair line(from, to);
lines.push_back(line);
from = to;
}
}
}
CMessageResult CxxGUIEdit::notify(CBaseObject* sender, const char* message)
{
if (message == CVSTGUITimer::kMsgTimer)
{
if (waveView != nullptr)
{
waveView->setWave(pSample32inL_Trans, int32NumSamples_Trans);
waveView->setDirty();
}
}
return VSTGUIEditor::notify(sender, message);
}
void CxxGUIEdit::setSingnal(Sample32** pSample32inL, Sample32** pSample32inR, int32 int32NumSamples) {
pSample32inL_Trans = pSample32inL;
pSample32inR_Trans = pSample32inR;
int32NumSamples_Trans = int32NumSamples;
}
} }