Skip to content

Instantly share code, notes, and snippets.

@kyab
Last active October 9, 2020 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyab/6b667beb3dab8b5666136f6335c86b85 to your computer and use it in GitHub Desktop.
Save kyab/6b667beb3dab8b5666136f6335c86b85 to your computer and use it in GitHub Desktop.
vst3host2 wip
typedef IPluginFactory* (*THEAPI)();
typedef bool (*BundleEntryFunc) (CFBundleRef);
//#define BUNDLE_PATH @"/Library/Audio/Plug-Ins/VST/OldSkoolVerb.vst3"
//#define BUNDLE_PATH @"/Library/Audio/Plug-Ins/VST/SPAN.vst3"
#define BUNDLE_PATH @"/Library/Audio/Plug-Ins/VST3/UpStereo.vst3"
//#define BUNDLE_PATH @"/Users/koji/work/VST_SDK/VST3_SDK/build/VST3/Debug/helloworld.vst3"
//#define BUNDLE_PATH @"/Library/Audio/Plug-Ins/VST3/TAL-Chorus-LX.vst3"
#define BUNDLE_PATH @"/Library/Audio/Plug-Ins/VST3/TDR VOS SlickEQ.vst3"
- (IBAction)testVST:(id)sender {
NSBundle *bundle = [NSBundle bundleWithPath:BUNDLE_PATH];
[bundle load];
NSLog(@"bundle name = %@", bundle);
tresult res = 0;
{
NSString *bundlePath = BUNDLE_PATH;
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
CFBundleRef cfBundle = NULL;
cfBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)bundleURL);
BundleEntryFunc entryFunc = (BundleEntryFunc)CFBundleGetFunctionPointerForName(cfBundle,CFSTR("bundleEntry"));
if (!entryFunc){
NSLog(@"Bundle does not export the required 'bundleEntry' function");
return;
}
bool ret = entryFunc(cfBundle);
NSLog(@"bundleEntry return %d", ret);
THEAPI func = (THEAPI)CFBundleGetFunctionPointerForName(cfBundle, CFSTR("GetPluginFactory"));
if(!func){
NSLog(@"GetPluginFactory() not found. Maybe not VST3 Plugin");
return;
}
IPluginFactory *pluginFactory = func();
if (!pluginFactory){
NSLog(@"failed with get IPluginFactory");
return;
}
NSLog(@"pluginFactory has %d classes.", pluginFactory->countClasses());
int32 classNum = pluginFactory->countClasses();
for (int i = 0; i < classNum; i++){
PClassInfo classInfo;
pluginFactory->getClassInfo(i, &classInfo);
NSLog(@"class[%d],%s,category=%s", i, classInfo.name, classInfo.category);
if (0 == strncmp(classInfo.category, kVstAudioEffectClass, strlen(kVstAudioEffectClass))){
FUnknown *iUnknown = NULL;
res = pluginFactory->createInstance(classInfo.cid, FUnknown::iid, (void **)&iUnknown);
if (res != kResultOk){
NSLog(@"createInstance failed with %d", res);
continue;
}
Vst::IComponent *iComponent = NULL;
res = iUnknown->queryInterface(Vst::IComponent::iid, (void **)&iComponent);
if (res != kResultOk){
NSLog(@"queryIntarface for IComponent failed with %d", res);
continue;
}
res = iComponent->initialize(&hostApplication);
if(res != kResultOk){
NSLog(@"failed with IComponent::initialize() %d", res);
continue;
}
res = iComponent->setActive(1);
if(res != kResultOk){
NSLog(@"failed with IComponent::setActive(1). %d", res);
}
//get IAudioProcessor and do some stuff.
Vst::IAudioProcessor *iAudioProcessor = NULL;
res = iUnknown->queryInterface(Vst::IAudioProcessor::iid, (void **)(&iAudioProcessor));
if (res != kResultOk){
NSLog(@"failed for queryInterface IAudioProcessor");
continue;
}
Vst::SpeakerArrangement sa = Vst::SpeakerArr::kStereo;
res = iAudioProcessor->setBusArrangements(&sa, 1, &sa, 1);
if (res != kResultOk){
NSLog(@"failed for setBusArrangements res = %d", res);
continue;
}
Vst::ProcessSetup processSetup;
processSetup.maxSamplesPerBlock =32;
processSetup.processMode = Vst::kRealtime;
processSetup.sampleRate = 44100;
processSetup.symbolicSampleSize = Vst::kSample32;
res = iAudioProcessor->setupProcessing(processSetup);
if (res != kResultOk){
NSLog(@"failed for setupProcessing res = %d", res);
continue;
}
//getControllerClassId()で取得したEditControllerのCIDを使ってcreateInstance()する
TUID controllerClassId;
res = iComponent->getControllerClassId(controllerClassId);
if (res != kResultOk){
NSLog(@"failed with getControllerClassId(fx) with %d", res);
continue;
}
Vst::IEditController *editController = NULL;
res = pluginFactory->createInstance(controllerClassId, Vst::IEditController::iid,
(void **)&editController);
if (editController){
[self IEditorControllerObtained:editController];
}else{
NSLog(@"failed for createInstance for controller, IEditController. res = %d", res);
continue;
}
}else if (0 == strncmp(classInfo.category, kVstComponentControllerClass, strlen(kVstComponentControllerClass))){
// FUnknown *iUnknown = NULL;
// res = pluginFactory->createInstance(classInfo.cid, FUnknown::iid, (void **)&iUnknown);
// if (iUnknown){
// Vst::IEditController *editController = NULL;
// res = iUnknown->queryInterface(Vst::IEditController::iid, (void **)&editController);
// if (editController){
// [self IEditorControllerObtained:editController];
// }
// }
}
}
}
}
//
-(void)IEditorControllerObtained:(Vst::IEditController *)editorController{
tresult res = 0;
//適当なやつを渡す。
res = editorController->initialize(&hostApplication);
if (res != kResultOk){
NSLog(@" failed with initialize");
return;
}else{
NSLog(@" OK to initialize");
}
//適当なやつを渡す。
res = editorController->setComponentHandler(&componentHandler);
if(res != kResultOk){
NSLog(@" failed with setComponentHandler");
return;
}else{
NSLog(@" OK to setComponentHandler");
}
//ここまではエラーなしで来ることを確認済み。
int32 c = editorController->getParameterCount();
NSLog(@" has %d parameters",c);
for (int i=0; i < c; i++){
Vst::ParameterInfo paramInfo = Vst::ParameterInfo();
res = editorController->getParameterInfo(i, paramInfo);
NSString *title = [[NSString alloc] initWithCharacters:(const unichar *)paramInfo.title length:128];
NSString *units = [[NSString alloc] initWithCharacters:(const unichar *)paramInfo.units length:128];
Vst::ParamID paramId = paramInfo.id;
Vst::ParamValue defNormalizedValue = paramInfo.defaultNormalizedValue;
NSLog(@" parameter[%d]:%@(%d)=%f%@", i, title, paramId,defNormalizedValue,units);
}
IPlugView *view = editorController->createView("editor");
NSLog(@" view = %p", view);
if(view){
res = view->isPlatformTypeSupported(kPlatformTypeNSView);
NSLog(@" isPlatformTypeSupported(NSView) res = %d", res);
res = view->attached((__bridge void *)_pluginEditorSuperView, kPlatformTypeNSView);
NSLog(@" attached res = %d",res);
}
}
//------------------------------------------------------------------------header for IHostApplication side
using namespace Steinberg;
class MyComponentHandler : public Vst::IComponentHandler, public Vst::IComponentHandler2
{
public:
tresult PLUGIN_API beginEdit (Vst::ParamID id) override
{
NSLog(@"beginEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API performEdit (Vst::ParamID id, Vst::ParamValue valueNormalized) override
{
NSLog(@"performEdit called (%d, %f)\n", id, valueNormalized);
return kNotImplemented;
}
tresult PLUGIN_API endEdit (Vst::ParamID id) override
{
NSLog(@"endEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API restartComponent (int32 flags) override
{
NSLog(@"restartComponent called (%d)\n", flags);
return kNotImplemented;
}
tresult PLUGIN_API setDirty (TBool state) override {
NSLog(@"setDirty called");
return kNotImplemented;
}
tresult PLUGIN_API requestOpenEditor (FIDString name = Vst::ViewType::kEditor) override {
NSLog(@"requestOpenEditor");
return kNotImplemented;
}
tresult PLUGIN_API startGroupEdit() override {
NSLog(@"startGroupEdit called");
return kNotImplemented;
}
/** Finishes the group editing started by a \ref startGroupEdit (call after a \ref IComponentHandler::endEdit). */
tresult PLUGIN_API finishGroupEdit () override {
NSLog(@"finishGroupEdit");
return kNotImplemented;
}
private:
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
{
NSUUID *uuid = [[NSUUID alloc] initWithUUIDBytes:(const unsigned char *)_iid];
NSLog(@"COmponentHandler::queryInterface get called for TUID:%@", uuid.UUIDString);
*obj = (Vst::IComponentHandler2 *)this;
return kResultOk;
}
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
};
namespace Steinberg{
namespace Vst{
class HostApplication : public IHostApplication
{
public:
HostApplication();
virtual ~HostApplication() { FUNKNOWN_DTOR }
//--- IHostApplication ---------------
tresult PLUGIN_API getName (String128 name) SMTG_OVERRIDE;
tresult PLUGIN_API createInstance (TUID cid, TUID _iid, void** obj) SMTG_OVERRIDE;
DECLARE_FUNKNOWN_METHODS
PlugInterfaceSupport* getPlugInterfaceSupport () const {
NSLog(@"HostApplication::getPlugInterfaceSupport called");
return mPlugInterfaceSupport;
}
protected:
IPtr<PlugInterfaceSupport> mPlugInterfaceSupport;
};
}
}
//-----------------------------------cpp for IComponent, IHostApplication
namespace Steinberg{
namespace Vst{
HostApplication::HostApplication ()
{
FUNKNOWN_CTOR
mPlugInterfaceSupport = owned (NEW PlugInterfaceSupport);
}
tresult PLUGIN_API HostApplication::getName (String128 name)
{
NSLog(@"hogehoge");
return kResultTrue;
}
tresult PLUGIN_API HostApplication::createInstance (TUID cid, TUID _iid, void** obj)
{
NSLog(@"HostApplication::createInstance");
FUID classID (FUID::fromTUID (cid));
FUID interfaceID (FUID::fromTUID (_iid));
if (classID == IMessage::iid && interfaceID == IMessage::iid)
{
NSLog(@"IMessage");
// *obj = new HostMessage;
return kResultTrue;
}
else if (classID == IAttributeList::iid && interfaceID == IAttributeList::iid)
{
NSLog(@"IAttributeList");
// *obj = new HostAttributeList;
return kResultTrue;
}
*obj = nullptr;
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostApplication::queryInterface (const char* _iid, void** obj)
{
NSUUID *uuid = [[NSUUID alloc] initWithUUIDBytes:(const unsigned char *)_iid];
NSLog(@"HostApplication::queryInterface get called for TUID:%@", uuid.UUIDString);
QUERY_INTERFACE (_iid, obj, FUnknown::iid, IHostApplication)
QUERY_INTERFACE (_iid, obj, IHostApplication::iid, IHostApplication)
if (mPlugInterfaceSupport && mPlugInterfaceSupport->queryInterface (iid, obj) == kResultTrue)
return kResultOk;
NSLog(@"failed ?");
*obj = nullptr;
return kResultFalse;
}
//-----------------------------------------------------------------------------
uint32 PLUGIN_API HostApplication::addRef ()
{
return 1;
}
//-----------------------------------------------------------------------------
uint32 PLUGIN_API HostApplication::release ()
{
return 1;
}
#include <algorithm>
//-----------------------------------------------------------------------------
PlugInterfaceSupport::PlugInterfaceSupport ()
{
// add minimum set
//---VST 3.0.0--------------------------------
addPlugInterfaceSupported (IComponent::iid);
addPlugInterfaceSupported (IAudioProcessor::iid);
addPlugInterfaceSupported (IEditController::iid);
addPlugInterfaceSupported (IConnectionPoint::iid);
addPlugInterfaceSupported (IUnitInfo::iid);
addPlugInterfaceSupported (IUnitData::iid);
addPlugInterfaceSupported (IProgramListData::iid);
//---VST 3.0.1--------------------------------
addPlugInterfaceSupported (IMidiMapping::iid);
//---VST 3.1----------------------------------
addPlugInterfaceSupported (IEditController2::iid);
/*
//---VST 3.0.2--------------------------------
addPlugInterfaceSupported (IParameterFinder::iid);
//---VST 3.1----------------------------------
addPlugInterfaceSupported (IAudioPresentationLatency::iid);
//---VST 3.5----------------------------------
addPlugInterfaceSupported (IKeyswitchController::iid);
addPlugInterfaceSupported (IContextMenuTarget::iid);
addPlugInterfaceSupported (IEditControllerHostEditing::iid);
addPlugInterfaceSupported (IXmlRepresentationController::iid);
addPlugInterfaceSupported (INoteExpressionController::iid);
//---VST 3.6.5--------------------------------
addPlugInterfaceSupported (ChannelContext::IInfoListener::iid);
addPlugInterfaceSupported (IPrefetchableSupport::iid);
addPlugInterfaceSupported (IAutomationState::iid);
//---VST 3.6.11--------------------------------
addPlugInterfaceSupported (INoteExpressionPhysicalUIMapping::iid);
//---VST 3.6.12--------------------------------
addPlugInterfaceSupported (IMidiLearn::iid);
//---VST 3.7-----------------------------------
addPlugInterfaceSupported (IProcessContextRequirements::iid);
addPlugInterfaceSupported (IParameterFunctionName::iid);
addPlugInterfaceSupported (IProgress::iid);
*/
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API PlugInterfaceSupport::isPlugInterfaceSupported (const TUID _iid)
{
auto uid = FUID::fromTUID (_iid);
if (std::find (mFUIDArray.begin (), mFUIDArray.end (), uid) != mFUIDArray.end ())
return kResultTrue;
return kResultFalse;
}
//-----------------------------------------------------------------------------
void PlugInterfaceSupport::addPlugInterfaceSupported (const TUID _iid)
{
mFUIDArray.push_back (FUID::fromTUID (_iid));
}
//-----------------------------------------------------------------------------
bool PlugInterfaceSupport::removePlugInterfaceSupported (const TUID _iid)
{
return std::remove (mFUIDArray.begin (), mFUIDArray.end (), FUID::fromTUID (_iid)) !=
mFUIDArray.end ();
}
}
}
@hotwatermorning
Copy link

hotwatermorning commented Oct 8, 2020

https://gist.github.com/kyab/6b667beb3dab8b5666136f6335c86b85#file-vst3host-wip-mm-L341

addRef, release() などの関数は、オブジェクトの寿命を管理するための仕組みとして用意されている関数で、参照カウントというものを増減させる役割があります。
ここが正しく実装されていないと、メモリリークを引き起こしたり、予期せずオブジェクトが delete されてしまったりして、プログラムが正しく動作しなくなる危険性があります。
(現状では、参照カウントが正しく増減されない実装になっているので、この問題が発生します)

ここは通常は特にカスタマイズする必要がない場合は、独自に実装するよりは、 IMPLEMENT_FUNKNOWN_METHODS や IMPLEMENT_REFCOUNT などの事前に用意されているマクロを使って実装したほうが良さそうです。

@hotwatermorning
Copy link

hotwatermorning commented Oct 8, 2020

ホストアプリケーションを開発する際のテクニックとして、 VST3 SDK に含まれるサンプルのプラグインを使ったデバッグ手法について紹介します。

VST3 SDK に含まれるサンプルプラグインは、 Visual Studio や Xcode のような IDE でビルドできるようになっています。
IDE に付属しているデバッガを使うと、任意のホストアプリケーション上にロードされたプラグインをデバッグできます。(*1)
手順は以下の通りです。

  1. IDE でサンプルプラグインをデバッグビルドし、ビルドしたプラグインを VST3 プラグインのインストールディレクトリに配置する。
  2. IDE のデバッグ設定で、デバッガをアタッチする実行ファイルを対象のホストアプリケーションにする。
  3. プラグインのソースコード上にブレークポイントを仕掛ける。
  4. デバッガを実行し、ホストアプリケーションを起動する。
  5. ホストアプリケーション上で、事前に配置したデバッグ版プラグインをロードする。

ここで 手順2 の対象のホストアプリケーションを、市販の DAW と自分のホストアプリケーションで切り替えながらデバッグを行うことで、プラグインから見えてくるホスト側の挙動の違いをこまかく調査できます。(自分の開発しているホストアプリケーションの挙動がなにか辺だったり、返してくるデータが変だったりして、他のホストアプリケーションと挙動が異なっていれば、そこが怪しいということになります)

ちょっとややこしい手順ですが、プラグイン側をデバッグして自分のホストアプリケーションの挙動をチェックするのは結構有用なので、覚えておいて損はないかと思います。

(*1) 最近の Mac だと、OS のセキュリティ機能によってデバッガをアタッチできない DAW があったりするので、そのときは、それを回避するための手順を踏むか別の DAW を使う必要があります。(確か Ableton Live とかはだめで、 Studio One とか Reaper は行けた気がします。)
参考: https://qiita.com/hotwatermorning/items/6a161d56356bfda9140e

@kyab
Copy link
Author

kyab commented Oct 8, 2020

参照カウントを正しく実装するようにします。
後デバッグも参考になります。 なぜかADelayとか使うとうまくパラメータ取れちゃった。。

@kyab
Copy link
Author

kyab commented Oct 8, 2020

市販(といってもフリーのいろいろ)だとダメ

@hotwatermorning
Copy link

hotwatermorning commented Oct 8, 2020

調べてみた感じ、 IEditController は単体だとパラメータ状態が取れないことがあるようで、先に Vst::IConnectionPoint を使って IComponent と相互接続をしておく必要がありました。手元で実験してみたところ、この接続処理をやってから getParameterCount() を呼び出せば正しくパラメータ数が取れました。

他には、次のような点が気になりました。

  • IComponent と IEditController が一つのコンポーネントで実装されている場合に、 getControllerClassId() では IEditController の CID が取れないので、そのケースに対処する必要がある。
  • ロード直後の IComponent の状態をもとに IEditController の状態を初期化するために、 IEditController の setComponentState() も呼び出しておいたほうがいい。

コードでいうと、こんな感じになりますねー

hotwatermorning/Late5-Studio@b07b002

@kyab
Copy link
Author

kyab commented Oct 9, 2020

動きました! kyab/Late5-Studio@233b276

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment