「VSTe VSTGUI inline UI Editor を使わないで、Viewに出力音声波形を描画する方法」の版間の差分

提供:yonewiki
編集の要約なし
編集の要約なし
1行目: 1行目:
== '''概要''' ==
== '''概要''' ==
 VSTGUI inline UI Editorの使い方って、あまり情報がないので、よくわからないかったのですが、とりあえず、使わない方法はかなり情報があったので、音声波形を描画することができました。
 VSTGUI inline UI Editorの使い方って、あまり情報がないので、よくわからないかったのですが、とりあえず、使わない方法はかなり情報があったので、音声波形を描画することができました。
[[ファイル:VSTGUI Waveform UI.png|400px|thumb|none|波形ViewのViewContainerControl]]





2023年6月17日 (土) 00:18時点における版

概要

 VSTGUI inline UI Editorの使い方って、あまり情報がないので、よくわからないかったのですが、とりあえず、使わない方法はかなり情報があったので、音声波形を描画することができました。


波形ViewのViewContainerControl


 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;
}

} }