Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------
MIT License
Copyright (c) 2025, Steinberg Media Technologies GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
//---------------------------------------------------------------------------------
+17
View File
@@ -0,0 +1,17 @@
# Welcome to VST 3 SDK public_sdk
Here are located:
- helper classes implementing **VST 3** Interfaces
- samples of **VST 3** Hosting and **VST 3** plug-ins
- **AAX** Wrapper
- **AU** Wrapper
- **AUv3** Wrapper
- InterAppAudio
## License & Usage guidelines
More details are found at [VST 3 SDK public_sdk License](https://forums.steinberg.net/t/vst-3-sdk-public-sdk-license/695592)
----
Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk)
@@ -0,0 +1,8 @@
if(ANDROID)
return()
endif(ANDROID)
include(SMTG_AddSubDirectories)
smtg_add_subdirectories()
@@ -0,0 +1,89 @@
if(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
include(SMTG_FindJack)
if(${LIBJACK_FOUND})
set(audiohost_sources
"${SDK_ROOT}/public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
"${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp"
"${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.h"
source/audiohost.cpp
source/audiohost.h
source/media/audioclient.cpp
source/media/audioclient.h
source/media/imediaserver.h
source/media/iparameterclient.h
source/media/jack/jackclient.cpp
source/media/miditovst.h
source/platform/appinit.h
source/usediids.cpp
)
if(SMTG_MAC)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm"
)
set_source_files_properties(
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
set(audiohost_PLATFORM_LIBS "-framework CoreFoundation")
get_filename_component(InfoPlistFile "resource/Info.plist" ABSOLUTE)
set(APP_PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${InfoPlistFile}
)
elseif(SMTG_LINUX)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp"
)
set(audiohost_PLATFORM_LIBS)
set(APP_PROPERTIES)
elseif(SMTG_WIN)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp"
)
set(audiohost_PLATFORM_LIBS)
if(MSVC)
set(APP_PROPERTIES
LINK_FLAGS
"/SUBSYSTEM:console"
)
endif(MSVC)
endif(SMTG_MAC)
set(target audiohost)
add_executable(${target} ${audiohost_sources})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
${audiohost_PLATFORM_LIBS}
${LIBJACK_LIBRARY}
)
target_include_directories(${target}
PRIVATE
${LIBJACK_INCLUDE_DIR}
)
if (APP_PROPERTIES)
set_target_properties(${target}
PROPERTIES
${APP_PROPERTIES}
)
endif(APP_PROPERTIES)
else()
message(STATUS "[SMTG] LIBJACK_FOUND is not set.")
endif(${LIBJACK_FOUND})
endif(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
@@ -0,0 +1,18 @@
# AudioHost Application
## Introduction
As Cross-platform source code: Simple cross-platform (only tested on Linux) host application allowing you to register a **VST 3** plug-in with Jack Server.
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/AudioHost.html#audiohost-application).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>audiohost</string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.vstsdk.audiohost</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>audiohost</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2024 Steinberg. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>vst3</string>
</array>
<key>CFBundleTypeName</key>
<string>VST3 Plug-In</string>
<key>CFBundleTypeRole</key>
<string>Audio</string>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeDescription</key>
<string>VST3 Plug-In</string>
<key>UTTypeIdentifier</key>
<string>com.steinberg.vst3.plugin</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vst3</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,158 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audiohost.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/audiohost/source/audiohost.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/platform/appinit.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "base/source/fcommandline.h"
#include "pluginterfaces/base/funknown.h"
#include "pluginterfaces/base/fstrdefs.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/gui/iplugviewcontentscalesupport.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/vsttypes.h"
#include <cstdio>
#include <iostream>
#if WIN32
#include "windows.h"
#include <wtypes.h>
#endif
//------------------------------------------------------------------------
namespace Steinberg {
FUnknown* gStandardPluginContext = new Vst::HostApplication ();
namespace Vst {
namespace AudioHost {
static AudioHost::AppInit gInit (std::make_unique<App> ());
//------------------------------------------------------------------------
App::~App () noexcept
{
}
//------------------------------------------------------------------------
void App::startAudioClient (const std::string& path, VST3::Optional<VST3::UID> effectID,
uint32 flags)
{
std::string error;
module = VST3::Hosting::Module::create (path, error);
if (!module)
{
std::string reason = "Could not create Module for file:";
reason += path;
reason += "\nError: ";
reason += error;
// EditorHost::IPlatform::instance ().kill (-1, reason);
return;
}
auto factory = module->getFactory ();
for (auto& classInfo : factory.classInfos ())
{
if (classInfo.category () == kVstAudioEffectClass)
{
if (effectID)
{
if (*effectID != classInfo.ID ())
continue;
}
plugProvider = owned (new PlugProvider (factory, classInfo, true));
break;
}
}
if (!plugProvider)
{
std::string error;
if (effectID)
error =
"No VST3 Audio Module Class with UID " + effectID->toString () + " found in file ";
else
error = "No VST3 Audio Module Class found in file ";
error += path;
// EditorHost::IPlatform::instance ().kill (-1, error);
return;
}
OPtr<IComponent> component = plugProvider->getComponent ();
OPtr<IEditController> controller = plugProvider->getController ();
auto midiMapping = U::cast<IMidiMapping> (controller);
//! TODO: Query the plugProvider for a proper name which gets displayed in JACK.
vst3Processor = AudioClient::create ("VST 3 SDK", component, midiMapping);
}
//------------------------------------------------------------------------
void App::init (const std::vector<std::string>& cmdArgs)
{
if (cmdArgs.empty ())
{
/*auto helpText = R"(
usage: AudioHost pluginPath
)";
*/
return;
}
VST3::Optional<VST3::UID> uid;
uint32 flags {};
startAudioClient (cmdArgs.back (), std::move (uid), flags);
}
//------------------------------------------------------------------------
void App::terminate ()
{
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
#if WIN32
int wmain (int argc, wchar_t* argv[])
{
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (Steinberg::Vst::StringConvert::convert (Steinberg::wscast (argv[i])));
Steinberg::Vst::AudioHost::gInit.app->init (cmdArgs);
std::cout << "Press <enter> to continue . . .";
std::getchar ();
return 0;
}
#else
int main (int argc, char* argv[])
{
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::AudioHost::gInit.app->init (cmdArgs);
std::cout << "Press <enter> to continue . . .";
std::getchar ();
return 0;
}
#endif
@@ -0,0 +1,54 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audiohost.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/audioclient.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/vst/utility/optional.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace AudioHost {
//------------------------------------------------------------------------
class App : public EditorHost::IApplication
{
public:
~App () noexcept override;
void init (const std::vector<std::string>& cmdArgs) override;
void terminate () override;
private:
enum OpenFlags
{
};
void startAudioClient (const std::string& path, VST3::Optional<VST3::UID> effectID,
uint32 flags);
VST3::Hosting::Module::Ptr module {nullptr};
IPtr<PlugProvider> plugProvider {nullptr};
AudioClientPtr vst3Processor;
};
//------------------------------------------------------------------------
} // AudioHost
} // Vst
} // Steinberg
@@ -0,0 +1,440 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/audioclient.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "audioclient.h"
#include "miditovst.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include <functional>
#include <cassert>
#include <limits>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
// From Vst2Wrapper
static MidiCCMapping initMidiCtrlerAssignment (IComponent* component, IMidiMapping* midiMapping)
{
MidiCCMapping midiCCMapping {};
if (!midiMapping || !component)
return midiCCMapping;
int32 busses = std::min<int32> (component->getBusCount (kEvent, kInput), kMaxMidiMappingBusses);
if (midiCCMapping[0][0].empty ())
{
for (int32 b = 0; b < busses; b++)
for (int32 i = 0; i < kMaxMidiChannels; i++)
midiCCMapping[b][i].resize (Vst::kCountCtrlNumber);
}
ParamID paramID;
for (int32 b = 0; b < busses; b++)
{
for (int16 ch = 0; ch < kMaxMidiChannels; ch++)
{
for (int32 i = 0; i < Vst::kCountCtrlNumber; i++)
{
paramID = kNoParamId;
if (midiMapping->getMidiControllerAssignment (b, ch, (CtrlNumber)i, paramID) ==
kResultTrue)
{
// TODO check if tag is associated to a parameter
midiCCMapping[b][ch][i] = paramID;
}
else
midiCCMapping[b][ch][i] = kNoParamId;
}
}
}
return midiCCMapping;
}
//------------------------------------------------------------------------
static void assignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData,
bool unassign = false)
{
// Set outputs
auto bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numOutputs; busIndex++)
{
auto channelCount = processData.outputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numOutputs)
{
processData.setChannelBuffer (BusDirections::kOutput, busIndex, chanIndex,
unassign ? nullptr : buffers.outputs[bufferIndex]);
bufferIndex++;
}
}
}
// Set inputs
bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numInputs; busIndex++)
{
auto channelCount = processData.inputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numInputs)
{
processData.setChannelBuffer (BusDirections::kInput, busIndex, chanIndex,
unassign ? nullptr : buffers.inputs[bufferIndex]);
bufferIndex++;
}
}
}
}
//------------------------------------------------------------------------
static void unassignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData)
{
assignBusBuffers (buffers, processData, true);
}
//------------------------------------------------------------------------
// Vst3Processor
//------------------------------------------------------------------------
AudioClient::AudioClient ()
{
}
//------------------------------------------------------------------------
AudioClient::~AudioClient ()
{
terminate ();
}
//------------------------------------------------------------------------
AudioClientPtr AudioClient::create (const Name& name, IComponent* component,
IMidiMapping* midiMapping)
{
auto newProcessor = std::make_shared<AudioClient> ();
newProcessor->initialize (name, component, midiMapping);
return newProcessor;
}
//------------------------------------------------------------------------
void AudioClient::initProcessContext ()
{
processContext = {};
processContext.tempo = 120;
}
//------------------------------------------------------------------------
void AudioClient::createLocalMediaServer (const Name& name)
{
mediaServer = createMediaServer (name);
mediaServer->registerAudioClient (this);
mediaServer->registerMidiClient (this);
}
//------------------------------------------------------------------------
bool AudioClient::initialize (const Name& name, IComponent* _component, IMidiMapping* midiMapping)
{
component = _component;
if (!component)
return false;
initProcessData ();
paramTransferrer.setMaxParameters (1000);
if (midiMapping)
midiCCMapping = initMidiCtrlerAssignment (component, midiMapping);
createLocalMediaServer (name);
return true;
}
//------------------------------------------------------------------------
void AudioClient::terminate ()
{
mediaServer = nullptr;
FUnknownPtr<IAudioProcessor> processor = component;
if (!processor)
return;
processor->setProcessing (false);
component->setActive (false);
}
//------------------------------------------------------------------------
void AudioClient::initProcessData ()
{
// processData.prepare will be done in setBlockSize
processData.inputEvents = &eventList;
processData.inputParameterChanges = &inputParameterChanges;
processData.processContext = &processContext;
initProcessContext ();
}
//------------------------------------------------------------------------
IMidiClient::IOSetup AudioClient::getMidiIOSetup () const
{
IMidiClient::IOSetup iosetup;
auto count = component->getBusCount (MediaTypes::kEvent, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kInput, i, info) != kResultOk)
continue;
auto busName = StringConvert::convert (info.name, 128);
iosetup.inputs.push_back (busName);
}
count = component->getBusCount (MediaTypes::kEvent, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
auto busName = StringConvert::convert (info.name, 128);
iosetup.outputs.push_back (busName);
}
return iosetup;
}
//------------------------------------------------------------------------
IAudioClient::IOSetup AudioClient::getIOSetup () const
{
IAudioClient::IOSetup iosetup;
auto count = component->getBusCount (MediaTypes::kAudio, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = StringConvert::convert (info.name, 128);
iosetup.outputs.push_back (channelName + " " + std::to_string (j));
}
}
count = component->getBusCount (MediaTypes::kAudio, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kInput, i, info) != kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = StringConvert::convert (info.name, 128);
iosetup.inputs.push_back (channelName + " " + std::to_string (j));
}
}
return iosetup;
}
//------------------------------------------------------------------------
void AudioClient::preprocess (Buffers& buffers, int64_t continousFrames)
{
processData.numSamples = buffers.numSamples;
processContext.continousTimeSamples = continousFrames;
assignBusBuffers (buffers, processData);
paramTransferrer.transferChangesTo (inputParameterChanges);
}
//------------------------------------------------------------------------
bool AudioClient::process (Buffers& buffers, int64_t continousFrames)
{
FUnknownPtr<IAudioProcessor> processor = component;
if (!processor || !isProcessing)
return false;
preprocess (buffers, continousFrames);
if (processor->process (processData) != kResultOk)
return false;
postprocess (buffers);
return true;
}
//------------------------------------------------------------------------
void AudioClient::postprocess (Buffers& buffers)
{
eventList.clear ();
inputParameterChanges.clearQueue ();
unassignBusBuffers (buffers, processData);
}
//------------------------------------------------------------------------
bool AudioClient::setSamplerate (SampleRate value)
{
if (sampleRate == value)
return true;
sampleRate = value;
processContext.sampleRate = sampleRate;
if (blockSize == 0)
return true;
return updateProcessSetup ();
}
//------------------------------------------------------------------------
bool AudioClient::setBlockSize (int32 value)
{
if (blockSize == value)
return true;
blockSize = value;
if (sampleRate == 0)
return true;
processData.prepare (*component, blockSize, kSample32);
return updateProcessSetup ();
}
//------------------------------------------------------------------------
bool AudioClient::updateProcessSetup ()
{
FUnknownPtr<IAudioProcessor> processor = component;
if (!processor)
return false;
if (isProcessing)
{
if (processor->setProcessing (false) != kResultOk)
return false;
if (component->setActive (false) != kResultOk)
return false;
}
ProcessSetup setup {kRealtime, kSample32, blockSize, sampleRate};
if (processor->setupProcessing (setup) != kResultOk)
return false;
if (component->setActive (true) != kResultOk)
return false;
processor->setProcessing (true); // != kResultOk
/*
if (processor->setProcessing(true) != kResultOk)
return false;*/
isProcessing = true;
return isProcessing;
}
//------------------------------------------------------------------------
bool AudioClient::isPortInRange (int32 port, int32 channel) const
{
return port < kMaxMidiMappingBusses && !midiCCMapping[port][channel].empty ();
}
//------------------------------------------------------------------------
bool AudioClient::processVstEvent (const IMidiClient::Event& event, int32 port)
{
auto vstEvent = midiToEvent (event.type, event.channel, event.data0, event.data1);
if (vstEvent)
{
vstEvent->busIndex = port;
if (eventList.addEvent (*vstEvent) != kResultOk)
{
assert (false && "Event was not added to EventList!");
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool AudioClient::processParamChange (const IMidiClient::Event& event, int32 port)
{
auto paramMapping = [port, this] (int32 channel, MidiData data1) -> ParamID {
if (!isPortInRange (port, channel))
return kNoParamId;
return midiCCMapping[port][channel][data1];
};
auto paramChange =
midiToParameter (event.type, event.channel, event.data0, event.data1, paramMapping);
if (paramChange)
{
int32 index = 0;
IParamValueQueue* queue =
inputParameterChanges.addParameterData ((*paramChange).first, index);
if (queue)
{
if (queue->addPoint (static_cast<int32> (event.timestamp), (*paramChange).second,
index) != kResultOk)
{
assert (false && "Parameter point was not added to ParamValueQueue!");
}
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool AudioClient::onEvent (const IMidiClient::Event& event, int32_t port)
{
// Try to create Event first.
if (processVstEvent (event, port))
return true;
// In case this is no event it must be a parameter.
if (processParamChange (event, port))
return true;
// TODO: Something else???
return true;
}
//------------------------------------------------------------------------
void AudioClient::setParameter (ParamID id, ParamValue value, int32 sampleOffset)
{
paramTransferrer.addChange (id, value, sampleOffset);
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audioclient.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include <array>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
class IMidiMapping;
class IComponent;
enum
{
kMaxMidiMappingBusses = 4,
kMaxMidiChannels = 16
};
using Controllers = std::vector<int32>;
using Channels = std::array<Controllers, kMaxMidiChannels>;
using Busses = std::array<Channels, kMaxMidiMappingBusses>;
using MidiCCMapping = Busses;
//------------------------------------------------------------------------
using AudioClientPtr = std::shared_ptr<class AudioClient>;
//------------------------------------------------------------------------
class AudioClient : public IAudioClient, public IMidiClient, public IParameterClient
{
public:
//--------------------------------------------------------------------
using Name = std::string;
AudioClient ();
~AudioClient () override;
static AudioClientPtr create (const Name& name, IComponent* component,
IMidiMapping* midiMapping);
// IAudioClient
bool process (Buffers& buffers, int64_t continousFrames) override;
bool setSamplerate (SampleRate value) override;
bool setBlockSize (int32 value) override;
IAudioClient::IOSetup getIOSetup () const override;
// IMidiClient
bool onEvent (const Event& event, int32_t port) override;
IMidiClient::IOSetup getMidiIOSetup () const override;
// IParameterClient
void setParameter (ParamID id, ParamValue value, int32 sampleOffset) override;
bool initialize (const Name& name, IComponent* component, IMidiMapping* midiMapping);
//--------------------------------------------------------------------
private:
void createLocalMediaServer (const Name& name);
void terminate ();
void updateBusBuffers (Buffers& buffers, HostProcessData& processData);
void initProcessData ();
void initProcessContext ();
bool updateProcessSetup ();
void preprocess (Buffers& buffers, int64_t continousFrames);
void postprocess (Buffers& buffers);
bool isPortInRange (int32 port, int32 channel) const;
bool processVstEvent (const IMidiClient::Event& event, int32 port);
bool processParamChange (const IMidiClient::Event& event, int32 port);
SampleRate sampleRate = 0;
int32 blockSize = 0;
HostProcessData processData;
ProcessContext processContext;
EventList eventList;
ParameterChanges inputParameterChanges;
IComponent* component = nullptr;
ParameterChangeTransfer paramTransferrer;
MidiCCMapping midiCCMapping;
IMediaServerPtr mediaServer;
bool isProcessing = false;
Name name;
};
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <pluginterfaces/vst/vsttypes.h>
//----------------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
using IOName = std::string;
using IONames = std::vector<IOName>;
using AudioClientName = std::string;
struct IAudioClient
{
struct Buffers
{
float** inputs;
int32_t numInputs;
float** outputs;
int32_t numOutputs;
int32_t numSamples;
};
struct IOSetup
{
IONames inputs;
IONames outputs;
};
virtual bool process (Buffers& buffers, int64_t continousFrames) = 0;
virtual bool setSamplerate (SampleRate value) = 0;
virtual bool setBlockSize (int32 value) = 0;
virtual IOSetup getIOSetup () const = 0;
virtual ~IAudioClient () {}
};
//----------------------------------------------------------------------------------
struct IMidiClient
{
using MidiData = uint8_t;
struct Event
{
MidiData type;
MidiData channel;
MidiData data0;
MidiData data1;
int64_t timestamp;
};
struct IOSetup
{
IONames inputs;
IONames outputs;
};
virtual bool onEvent (const Event& event, int32_t port) = 0;
virtual IOSetup getMidiIOSetup () const = 0;
virtual ~IMidiClient () {}
};
//----------------------------------------------------------------------------------
struct IMediaServer
{
virtual bool registerAudioClient (IAudioClient* client) = 0;
virtual bool registerMidiClient (IMidiClient* client) = 0;
virtual ~IMediaServer () {}
};
//----------------------------------------------------------------------------------
using IMediaServerPtr = std::shared_ptr<IMediaServer>;
IMediaServerPtr createMediaServer (const AudioClientName& name);
//----------------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,39 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include <memory>
#include <pluginterfaces/vst/vsttypes.h>
//----------------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//----------------------------------------------------------------------------------
struct IParameterClient
{
virtual void setParameter (ParamID id, ParamValue value, int32 sampleOffset) = 0;
virtual ~IParameterClient () {}
};
//----------------------------------------------------------------------------------
using IParameterClientPtr = std::weak_ptr<IParameterClient>;
//----------------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,405 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/jack/jackclient.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3 using Jack
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
#include <cassert>
//! Workaround for Jack on Windows
#if defined(SMTG_OS_WINDOWS) && defined(_STDINT)
#define _STDINT_H
#endif
#include <jack/jack.h>
#include <jack/midiport.h>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
static const int kJackSuccess = 0;
//------------------------------------------------------------------------
// jack Client
//------------------------------------------------------------------------
class JackClient : public IMediaServer
{
public:
//--------------------------------------------------------------------
using JackPorts = std::vector<jack_port_t*>;
using JackName = std::string;
JackClient () = default;
~JackClient () override;
// IMediaServer interface
bool registerAudioClient (IAudioClient* client) override;
bool registerMidiClient (IMidiClient* client) override;
bool initialize (JackName name);
// jack process callback
int process (jack_nframes_t nframes);
//--------------------------------------------------------------------
private:
jack_client_t* registerClient (JackName name);
bool registerAudioPorts (IAudioClient* processor);
bool registerMidiPorts (IMidiClient* processor);
bool addAudioOutputPort (JackName name);
bool addAudioInputPort (JackName name);
bool addMidiInputPort (JackName name);
int processMidi (jack_nframes_t nframes);
bool setupJackProcessCallbacks (jack_client_t* client);
bool autoConnectAudioPorts (jack_client_t* client);
bool autoConnectMidiPorts (jack_client_t* client);
void updateAudioBuffers (jack_nframes_t nframes);
// Jack objects
jack_client_t* jackClient = nullptr;
JackPorts audioOutputPorts;
JackPorts audioInputPorts;
JackPorts midiInputPorts;
IAudioClient* audioClient = nullptr;
IMidiClient* midiClient = nullptr;
using BufferPointers = std::vector<jack_default_audio_sample_t*>;
BufferPointers audioOutputPointers;
BufferPointers audioInputPointers;
IAudioClient::Buffers buffers {nullptr};
};
//------------------------------------------------------------------------
int jack_on_process (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast<JackClient*> (arg);
return client->process (nframes);
}
//------------------------------------------------------------------------
int jack_on_set_sample_rate (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast<IAudioClient*> (arg);
client->setSamplerate (static_cast<SampleRate> (nframes));
return kJackSuccess;
}
//------------------------------------------------------------------------
int jack_on_set_block_size (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast<IAudioClient*> (arg);
client->setBlockSize (static_cast<int32> (nframes));
return kJackSuccess;
}
//------------------------------------------------------------------------
IMediaServerPtr createMediaServer (const AudioClientName& name)
{
auto client = std::make_shared<JackClient> ();
client->initialize (name);
return client;
}
//------------------------------------------------------------------------
JackClient::~JackClient ()
{
//! We do not need to "unregister" ports. It is done automatically with "jack_client_close"
jack_deactivate (jackClient); // Stops calls of process
jack_client_close (jackClient); // Remove client from process graph and remove all ports
}
//------------------------------------------------------------------------
bool JackClient::registerAudioClient (IAudioClient* client)
{
if (audioClient)
return false;
audioClient = client;
//! First thing to do: register the audio ports.
if (!registerAudioPorts (audioClient))
return false;
//! Setup all the callbacks like setSampleRate and process etc.
if (!setupJackProcessCallbacks (jackClient))
return false;
//! Activate after defining the callbacks. It is said in the documentation.
if (jack_activate (jackClient) != kJackSuccess)
return false;
//! AFTER activation, register the ports.
if (!autoConnectAudioPorts (jackClient))
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::registerMidiClient (IMidiClient* client)
{
if (midiClient)
return false;
midiClient = client;
//! Register the midi ports.
if (!registerMidiPorts (midiClient))
return false;
//! Afterwards auto-connect them.
if (!autoConnectMidiPorts (jackClient))
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::initialize (JackClient::JackName name)
{
jackClient = registerClient (name);
if (!jackClient)
return false;
return true;
}
//------------------------------------------------------------------------
void JackClient::updateAudioBuffers (jack_nframes_t nframes)
{
int outputIndex = 0;
for (auto audioOutputPort : audioOutputPorts)
{
auto* portBuffer = jack_port_get_buffer (audioOutputPort, nframes);
if (!portBuffer)
continue;
buffers.outputs[outputIndex++] = static_cast<jack_default_audio_sample_t*> (portBuffer);
}
int inputIndex = 0;
for (auto audioInputPort : audioInputPorts)
{
auto* portBuffer = jack_port_get_buffer (audioInputPort, nframes);
if (!portBuffer)
continue;
buffers.inputs[inputIndex++] = static_cast<jack_default_audio_sample_t*> (portBuffer);
}
}
//------------------------------------------------------------------------
int JackClient::process (jack_nframes_t nframes)
{
processMidi (nframes);
buffers.numSamples = nframes;
updateAudioBuffers (nframes);
if (!audioClient)
return 0;
if (audioClient->process (buffers, jack_last_frame_time (jackClient)) == false)
{
assert (false);
}
return kJackSuccess;
}
//------------------------------------------------------------------------
bool JackClient::registerAudioPorts (IAudioClient* processor)
{
auto ioSetup = processor->getIOSetup ();
for (const auto& output : ioSetup.outputs)
addAudioOutputPort (output);
for (const auto& input : ioSetup.inputs)
addAudioInputPort (input);
buffers.inputs = audioInputPointers.data ();
buffers.numInputs = (int32_t)audioInputPointers.size ();
buffers.numOutputs = (int32_t)audioOutputPointers.size ();
buffers.outputs = audioOutputPointers.data ();
return true;
}
//------------------------------------------------------------------------
bool JackClient::registerMidiPorts (IMidiClient* processor)
{
const auto ioSetup = processor->getMidiIOSetup ();
for (const auto& input : ioSetup.inputs)
addMidiInputPort (input);
return true;
}
//------------------------------------------------------------------------
bool JackClient::addAudioOutputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (!port)
return false;
audioOutputPorts.push_back (port);
audioOutputPointers.resize (audioOutputPorts.size ());
return true;
}
//------------------------------------------------------------------------
bool JackClient::addAudioInputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
if (!port)
return false;
audioInputPorts.push_back (port);
audioInputPointers.resize (audioOutputPorts.size ());
return true;
}
//------------------------------------------------------------------------
bool JackClient::addMidiInputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
if (!port)
return false;
midiInputPorts.push_back (port);
return true;
}
//------------------------------------------------------------------------
int JackClient::processMidi (jack_nframes_t nframes)
{
static const uint8_t kChannelMask = 0x0F;
static const uint8_t kStatusMask = 0xF0;
static const uint32_t kDataMask = 0x7F;
for (int32_t portIndex = 0, count = static_cast<int32_t> (midiInputPorts.size ());
portIndex < count; ++portIndex)
{
auto midiInputPort = midiInputPorts.at (portIndex);
auto* portBuffer = jack_port_get_buffer (midiInputPort, nframes);
if (!portBuffer)
continue;
jack_midi_event_t in_event;
auto event_count = jack_midi_get_event_count (portBuffer);
for (uint32_t i = 0; i < event_count; i++)
{
jack_midi_event_get (&in_event, portBuffer, i);
if (in_event.size == 0)
continue;
auto midiData = in_event.buffer;
Steinberg::Vst::IMidiClient::MidiData channel = midiData[0] & kChannelMask;
Steinberg::Vst::IMidiClient::MidiData status = midiData[0] & kStatusMask;
Steinberg::Vst::IMidiClient::MidiData data0 = midiData[1];
Steinberg::Vst::IMidiClient::MidiData data1 = midiData[2];
midiClient->onEvent ({status, channel, data0, data1, in_event.time}, portIndex);
}
}
return kJackSuccess;
}
//------------------------------------------------------------------------
jack_client_t* JackClient::registerClient (JackClient::JackName name)
{
jack_options_t options = JackNullOption;
jack_status_t status;
jackClient = jack_client_open (name.data (), options, &status, nullptr);
return jackClient;
/* Use the status to check for errors:
if (status & JackServerFailed)
{
fprintf (stderr, "Unable to connect to JACK server\n");
}*/
}
//------------------------------------------------------------------------
bool JackClient::setupJackProcessCallbacks (jack_client_t* client)
{
if (jack_set_process_callback (client, jack_on_process, this) != kJackSuccess)
return false;
if (jack_set_sample_rate_callback (client, jack_on_set_sample_rate, audioClient) !=
kJackSuccess)
return false;
if (jack_set_buffer_size_callback (client, jack_on_set_block_size, audioClient) != kJackSuccess)
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::autoConnectAudioPorts (jack_client_t* client)
{
int portIndex = 0;
//! Connect Audio Outputs
auto ports = jack_get_ports (client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
for (auto& port : audioOutputPorts)
{
if (!ports[portIndex])
break;
auto output__port_name = ports[portIndex++];
auto res = jack_connect (client, jack_port_name (port), output__port_name);
if (res != 0)
break;
}
jack_free (ports);
return true;
}
//------------------------------------------------------------------------
bool JackClient::autoConnectMidiPorts (jack_client_t* client)
{
int portIndex = 1;
//! Connect MIDI Inputs
auto ports = jack_get_ports (client, nullptr, "midi", JackPortIsPhysical | JackPortIsOutput);
if (!ports)
return false;
for (auto& port : midiInputPorts)
{
if (!ports[portIndex])
break;
auto inputPortName = ports[portIndex++];
auto res = jack_connect (client, inputPortName, jack_port_name (port));
if (res != 0)
continue;
}
jack_free (ports);
return true;
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,139 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/miditovst.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/utility/optional.h"
#include "pluginterfaces/vst/ivstevents.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include <functional>
#include <limits>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
const uint8_t kNoteOff = 0x80; ///< note, off velocity
const uint8_t kNoteOn = 0x90; ///< note, on velocity
const uint8_t kPolyPressure = 0xA0; ///< note, pressure
const uint8_t kController = 0xB0; ///< controller, value
const uint8_t kProgramChangeStatus = 0xC0; ///< program change
const uint8_t kAfterTouchStatus = 0xD0; ///< channel pressure
const uint8_t kPitchBendStatus = 0xE0; ///< lsb, msb
static const uint32 kDataMask = 0x7F;
const float kMidiScaler = 1.f / 127.f;
using MidiData = uint8_t;
float toNormalized (const MidiData& data)
{
return (float)data * kMidiScaler;
}
using OptionalEvent = VST3::Optional<Event>;
using ParameterChange = std::pair<ParamID, ParamValue>;
using OptionParamChange = VST3::Optional<ParameterChange>;
OptionalEvent midiToEvent (MidiData status, MidiData channel, MidiData midiData0,
MidiData midiData1)
{
Event new_event = {};
if (status == kNoteOn || status == kNoteOff)
{
if (status == kNoteOff) // note off
{
new_event.noteOff.noteId = -1;
new_event.type = Event::kNoteOffEvent;
new_event.noteOff.channel = channel;
new_event.noteOff.pitch = midiData0;
new_event.noteOff.velocity = toNormalized (midiData1);
return new_event;
}
if (status == kNoteOn) // note on
{
new_event.noteOn.noteId = -1;
new_event.type = Event::kNoteOnEvent;
new_event.noteOn.channel = channel;
new_event.noteOn.pitch = midiData0;
new_event.noteOn.velocity = toNormalized (midiData1);
return new_event;
}
}
//--- -----------------------------
else if (status == kPolyPressure)
{
new_event.type = Vst::Event::kPolyPressureEvent;
new_event.polyPressure.channel = channel;
new_event.polyPressure.pitch = midiData0;
new_event.polyPressure.pressure = toNormalized (midiData1);
return new_event;
}
return {};
}
//------------------------------------------------------------------------
using ToParameterIdFunc = std::function<ParamID (int32, MidiData)>;
OptionParamChange midiToParameter (MidiData status, MidiData channel, MidiData midiData1,
MidiData midiData2, const ToParameterIdFunc& toParamID)
{
if (!toParamID)
return {};
ParameterChange paramChange;
if (status == kController) // controller
{
paramChange.first = toParamID (channel, midiData1);
if (paramChange.first != kNoParamId)
{
paramChange.second = (double)midiData2 * kMidiScaler;
return paramChange;
}
}
else if (status == kPitchBendStatus)
{
paramChange.first = toParamID (channel, Vst::kPitchBend);
if (paramChange.first != kNoParamId)
{
const double kPitchWheelScaler = 1. / (double)0x3FFF;
const int32 ctrl = (midiData1 & kDataMask) | (midiData2 & kDataMask) << 7;
paramChange.second = kPitchWheelScaler * (double)ctrl;
return paramChange;
};
}
else if (status == kAfterTouchStatus)
{
paramChange.first = toParamID (channel, Vst::kAfterTouch);
if (paramChange.first != kNoParamId)
{
paramChange.second = (ParamValue) (midiData1 & kDataMask) * kMidiScaler;
return paramChange;
};
}
else if (status == kProgramChangeStatus)
{
// TODO
}
return {};
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,38 @@
//------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/platform/appinit.h
// Created by : Steinberg, 04/2005
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace AudioHost {
//------------------------------------------------------------------------
struct AppInit
{
explicit AppInit (EditorHost::ApplicationPtr&& _app) { app = std::move (_app); }
EditorHost::ApplicationPtr app;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/usediids.cpp
// Created by : Steinberg 09.2008
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
//#define INIT_CLASS_IID
// This macro definition modifies the behavior of DECLARE_CLASS_IID (funknown.h)
// and produces the actual symbols for all interface identifiers.
// It must be defined before including the interface headers and
// in only one source file!
//------------------------------------------------------------------------
//#define INIT_CLASS_IID
@@ -0,0 +1,159 @@
if(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
set(editorhost_sources
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.h
source/editorhost.cpp
source/editorhost.h
source/platform/appinit.h
source/platform/iapplication.h
source/platform/iplatform.h
source/platform/iwindow.h
source/usediids.cpp
)
if(SMTG_MAC)
set(editorhost_sources
${editorhost_sources}
source/platform/mac/platform.mm
source/platform/mac/window.h
source/platform/mac/window.mm
${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm
)
set_source_files_properties(
source/platform/mac/window.mm
source/platform/mac/platform.mm
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
set(editorhost_PLATFORM_LIBS
"-framework Cocoa"
)
get_filename_component(InfoPlistFile "resource/Info.plist" ABSOLUTE)
set(APP_PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${InfoPlistFile}
)
elseif(SMTG_LINUX)
# X11 files and configuration
find_package(X11 REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
pkg_check_modules(GTKMM3 REQUIRED gtkmm-3.0)
include_directories(${X11_INCLUDE_DIR} ${GTKMM3_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS})
set(editorhost_sources
${editorhost_sources}
source/platform/linux/platform.cpp
source/platform/linux/runloop.cpp
source/platform/linux/runloop.h
source/platform/linux/window.cpp
source/platform/linux/window.h
${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp
)
set(editorhost_PLATFORM_LIBS
${X11_LIBRARIES}
${GTK3_LIBRARIES}
${GTKMM3_LIBRARIES}
)
set(APP_PROPERTIES
)
# Wayland files and configuration
if(SMTG_ENABLE_WAYLAND_SUPPORT)
# Fetch wayland-server-delegate to get iwaylandclientcontext.h
include (FetchContent)
FetchContent_Declare(
ccl_wayland_server_delegate
GIT_REPOSITORY https://github.com/cclsoftware/wayland-server-delegate.git
GIT_TAG 86c32c61f0c861448635c041da5c343ce097ccff
)
FetchContent_MakeAvailable(ccl_wayland_server_delegate)
# Modify the wayland-server-delegate source directory, because it is needed to build without errors.
set(WAYLAND_SERVER_DELEGATE_FOLDER_NAME "wayland-server-delegate")
execute_process(COMMAND
${CMAKE_COMMAND} -E create_symlink ${ccl_wayland_server_delegate_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}
)
list (APPEND CMAKE_PREFIX_PATH "${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}/cmake")
list (APPEND CMAKE_MODULE_PATH "${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}/cmake")
list (APPEND WAYLAND_PROTOCOLS
"stable/xdg-shell/xdg-shell.xml"
"stable/linux-dmabuf/linux-dmabuf-v1.xml"
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
)
find_package (wayland-server-delegate)
set(editorhost_sources
${editorhost_sources}
source/platform/linux/wayland/clientcontext.cpp
source/platform/linux/wayland/clientcontext.h
source/platform/linux/wayland/platform.cpp
source/platform/linux/wayland/runloop.cpp
source/platform/linux/wayland/runloop.h
source/platform/linux/wayland/surfacedelegate.cpp
source/platform/linux/wayland/surfacedelegate.h
source/platform/linux/wayland/window.cpp
source/platform/linux/wayland/window.h
)
set(editorhost_PLATFORM_LIBS
${editorhost_PLATFORM_LIBS}
wayland-server-delegate
)
endif(SMTG_ENABLE_WAYLAND_SUPPORT)
elseif(SMTG_WIN)
set(editorhost_sources
${editorhost_sources}
source/platform/win32/platform.cpp
source/platform/win32/window.cpp
source/platform/win32/window.h
${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp
)
set(editorhost_PLATFORM_LIBS
)
if(MSVC)
set(APP_PROPERTIES
LINK_FLAGS "/SUBSYSTEM:windows"
)
endif(MSVC)
if(MINGW)
# It causes the UNICODE preprocessor macro to be predefined,
# and chooses Unicode-capable runtime startup code.
set(APP_PROPERTIES
LINK_FLAGS -municode
)
endif(MINGW)
endif(SMTG_MAC)
set(target editorhost)
add_executable(${target} ${editorhost_sources})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
${editorhost_PLATFORM_LIBS}
)
smtg_target_setup_universal_binary(${target})
if (APP_PROPERTIES)
set_target_properties(${target}
PROPERTIES
${APP_PROPERTIES}
)
endif(APP_PROPERTIES)
if(SMTG_LINUX AND SMTG_ENABLE_WAYLAND_SUPPORT)
target_compile_definitions(${target}
PRIVATE
SMTG_EDITOR_HOST_USE_WAYLAND=1
)
endif()
endif(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
@@ -0,0 +1,18 @@
# EditorHost Application
## Introduction
As Cross-platform source code: Simple cross-platform (Win/macOS/Linux) host application allowing you to open the editor of a **VST 3** plug-in (with HiDPI support on Windows/macOS).
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/EditorHost.html).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>editorhost</string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.vstsdk.editorhost</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>editorhost</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Steinberg. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>vst3</string>
</array>
<key>CFBundleTypeName</key>
<string>VST3 Plug-In</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeDescription</key>
<string>VST3 Plug-In</string>
<key>UTTypeIdentifier</key>
<string>com.steinberg.vst3.plugin</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vst3</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,426 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/editorhost.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/appinit.h"
#include "base/source/fcommandline.h"
#include "base/source/fdebug.h"
#include "pluginterfaces/base/funknown.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/gui/iplugviewcontentscalesupport.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/vsttypes.h"
#include <cstdio>
#include <iostream>
//------------------------------------------------------------------------
namespace Steinberg {
//------------------------------------------------------------------------
inline bool operator== (const ViewRect& r1, const ViewRect& r2)
{
return memcmp (&r1, &r2, sizeof (ViewRect)) == 0;
}
//------------------------------------------------------------------------
inline bool operator!= (const ViewRect& r1, const ViewRect& r2)
{
return !(r1 == r2);
}
namespace Vst {
namespace EditorHost {
static AppInit gInit (std::make_unique<App> ());
//------------------------------------------------------------------------
class WindowController : public IWindowController, public IPlugFrame
{
public:
WindowController (const IPtr<IPlugView>& plugView);
~WindowController () noexcept override;
void onShow (IWindow& w) override;
void onClose (IWindow& w) override;
void onResize (IWindow& w, Size newSize) override;
Size constrainSize (IWindow& w, Size requestedSize) override;
void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) override;
// IPlugFrame
tresult PLUGIN_API resizeView (IPlugView* view, ViewRect* newSize) override;
void closePlugView ();
private:
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
{
if (FUnknownPrivate::iidEqual (_iid, IPlugFrame::iid) ||
FUnknownPrivate::iidEqual (_iid, FUnknown::iid))
{
*obj = this;
addRef ();
return kResultTrue;
}
if (window)
return window->queryInterface (_iid, obj);
return kNoInterface;
}
// we do not care here of the ref-counting. A plug-in call of release should not destroy this
// class!
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
IPtr<IPlugView> plugView;
IWindow* window {nullptr};
bool resizeViewRecursionGard {false};
};
//------------------------------------------------------------------------
class ComponentHandler : public IComponentHandler
{
public:
tresult PLUGIN_API beginEdit (ParamID id) override
{
SMTG_DBPRT1 ("beginEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API performEdit (ParamID id, ParamValue valueNormalized) override
{
SMTG_DBPRT2 ("performEdit called (%d, %f)\n", id, valueNormalized);
return kNotImplemented;
}
tresult PLUGIN_API endEdit (ParamID id) override
{
SMTG_DBPRT1 ("endEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API restartComponent (int32 flags) override
{
SMTG_DBPRT1 ("restartComponent called (%d)\n", flags);
return kNotImplemented;
}
private:
tresult PLUGIN_API queryInterface (const TUID /*_iid*/, void** /*obj*/) override
{
return kNoInterface;
}
// we do not care here of the ref-counting. A plug-in call of release should not destroy this
// class!
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
};
static ComponentHandler gComponentHandler;
//------------------------------------------------------------------------
App::~App () noexcept
{
terminate ();
}
//------------------------------------------------------------------------
void App::openEditor (const std::string& path, VST3::Optional<VST3::UID> effectID, uint32 flags)
{
std::string error;
module = VST3::Hosting::Module::create (path, error);
if (!module)
{
std::string reason = "Could not create Module for file:";
reason += path;
reason += "\nError: ";
reason += error;
IPlatform::instance ().kill (-1, reason);
}
auto factory = module->getFactory ();
if (auto factoryHostContext = IPlatform::instance ().getPluginFactoryContext ())
factory.setHostContext (factoryHostContext);
for (auto& classInfo : factory.classInfos ())
{
if (classInfo.category () == kVstAudioEffectClass)
{
if (effectID)
{
if (*effectID != classInfo.ID ())
continue;
}
plugProvider = owned (new PlugProvider (factory, classInfo, true));
if (plugProvider->initialize () == false)
plugProvider = nullptr;
break;
}
}
if (!plugProvider)
{
if (effectID)
error =
"No VST3 Audio Module Class with UID " + effectID->toString () + " found in file ";
else
error = "No VST3 Audio Module Class found in file ";
error += path;
IPlatform::instance ().kill (-1, error);
}
auto editController = plugProvider->getController ();
if (!editController)
{
error = "No EditController found (needed for allowing editor) in file " + path;
IPlatform::instance ().kill (-1, error);
}
editController->release (); // plugProvider does an addRef
if (flags & kSetComponentHandler)
{
SMTG_DBPRT0 ("setComponentHandler is used\n");
editController->setComponentHandler (&gComponentHandler);
}
SMTG_DBPRT1 ("Open Editor for %s...\n", path.c_str ());
createViewAndShow (editController);
if (flags & kSecondWindow)
{
SMTG_DBPRT0 ("Open 2cd Editor...\n");
createViewAndShow (editController);
}
}
//------------------------------------------------------------------------
void App::createViewAndShow (IEditController* controller)
{
auto view = owned (controller->createView (ViewType::kEditor));
if (!view)
{
IPlatform::instance ().kill (-1, "EditController does not provide its own editor");
}
ViewRect plugViewSize {};
auto result = view->getSize (&plugViewSize);
if (result != kResultTrue)
{
IPlatform::instance ().kill (-1, "Could not get editor view size");
}
auto viewRect = ViewRectToRect (plugViewSize);
windowController = std::make_shared<WindowController> (view);
window = IPlatform::instance ().createWindow (
"Editor", viewRect.size, view->canResize () == kResultTrue, windowController);
if (!window)
{
IPlatform::instance ().kill (-1, "Could not create window");
}
window->show ();
}
//------------------------------------------------------------------------
void App::init (const std::vector<std::string>& cmdArgs)
{
VST3::Optional<VST3::UID> uid;
uint32 flags {};
for (auto it = cmdArgs.begin (), end = cmdArgs.end (); it != end; ++it)
{
if (*it == "--componentHandler")
flags |= kSetComponentHandler;
else if (*it == "--secondWindow")
flags |= kSecondWindow;
else if (*it == "--uid")
{
if (++it != end)
uid = VST3::UID::fromString (*it);
if (!uid)
IPlatform::instance ().kill (-1, "wrong argument to --uid");
}
}
if (cmdArgs.empty () || cmdArgs.back ().find (".vst3") == std::string::npos)
{
auto helpText = R"(
usage: EditorHost [options] pluginPath
options:
--componentHandler
set optional component handler on edit controller
--secondWindow
create a second window
--uid UID
use effect class with unique class ID==UID
)";
IPlatform::instance ().kill (0, helpText);
}
PluginContextFactory::instance ().setPluginContext (&pluginContext);
openEditor (cmdArgs.back (), std::move (uid), flags);
}
//------------------------------------------------------------------------
void App::terminate ()
{
if (windowController)
windowController->closePlugView ();
windowController.reset ();
plugProvider.reset ();
module.reset ();
PluginContextFactory::instance ().setPluginContext (nullptr);
}
//------------------------------------------------------------------------
WindowController::WindowController (const IPtr<IPlugView>& plugView) : plugView (plugView)
{
}
//------------------------------------------------------------------------
WindowController::~WindowController () noexcept
{
}
//------------------------------------------------------------------------
void WindowController::onShow (IWindow& w)
{
SMTG_DBPRT1 ("onShow called (%p)\n", (void*)&w);
window = &w;
if (!plugView)
return;
auto platformWindow = window->getNativePlatformWindow ();
if (plugView->isPlatformTypeSupported (platformWindow.type) != kResultTrue)
{
IPlatform::instance ().kill (-1, std::string ("PlugView does not support platform type:") +
platformWindow.type);
}
plugView->setFrame (this);
if (plugView->attached (platformWindow.ptr, platformWindow.type) != kResultTrue)
{
IPlatform::instance ().kill (-1, "Attaching PlugView failed");
}
}
//------------------------------------------------------------------------
void WindowController::closePlugView ()
{
if (plugView)
{
plugView->setFrame (nullptr);
if (plugView->removed () != kResultTrue)
{
IPlatform::instance ().kill (-1, "Removing PlugView failed");
}
plugView = nullptr;
}
window = nullptr;
}
//------------------------------------------------------------------------
void WindowController::onClose (IWindow& w)
{
SMTG_DBPRT1 ("onClose called (%p)\n", (void*)&w);
closePlugView ();
// TODO maybe quit only when the last window is closed
IPlatform::instance ().quit ();
}
//------------------------------------------------------------------------
void WindowController::onResize (IWindow& w, Size newSize)
{
SMTG_DBPRT1 ("onResize called (%p)\n", (void*)&w);
if (plugView)
{
ViewRect r {};
r.right = newSize.width;
r.bottom = newSize.height;
ViewRect r2 {};
if (plugView->getSize (&r2) == kResultTrue && r != r2)
plugView->onSize (&r);
}
}
//------------------------------------------------------------------------
Size WindowController::constrainSize (IWindow& w, Size requestedSize)
{
SMTG_DBPRT1 ("constrainSize called (%p)\n", (void*)&w);
if (plugView)
{
ViewRect r {};
r.right = requestedSize.width;
r.bottom = requestedSize.height;
if (plugView->checkSizeConstraint (&r) != kResultTrue)
{
requestedSize.width = r.right - r.left;
requestedSize.height = r.bottom - r.top;
}
}
return requestedSize;
}
//------------------------------------------------------------------------
void WindowController::onContentScaleFactorChanged (IWindow& w, float newScaleFactor)
{
SMTG_DBPRT1 ("onContentScaleFactorChanged called (%p)\n", (void*)&w);
if (auto css = U::cast<IPlugViewContentScaleSupport> (plugView))
{
css->setContentScaleFactor (newScaleFactor);
}
}
//------------------------------------------------------------------------
tresult PLUGIN_API WindowController::resizeView (IPlugView* view, ViewRect* newSize)
{
SMTG_DBPRT1 ("resizeView called (%p)\n", (void*)view);
if (newSize == nullptr || view == nullptr || view != plugView)
return kInvalidArgument;
if (!window)
return kInternalError;
if (resizeViewRecursionGard)
return kResultFalse;
ViewRect r;
if (plugView->getSize (&r) != kResultTrue)
return kInternalError;
if (r == *newSize)
return kResultTrue;
resizeViewRecursionGard = true;
Size size {newSize->right - newSize->left, newSize->bottom - newSize->top};
window->resize (size);
resizeViewRecursionGard = false;
if (plugView->getSize (&r) != kResultTrue)
return kInternalError;
if (r != *newSize)
plugView->onSize (newSize);
return kResultTrue;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.h
// Created by : Steinberg 09.2016
// Description : Example of opening a Plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/vst/utility/optional.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
class WindowController;
//------------------------------------------------------------------------
class App : public IApplication
{
public:
~App () noexcept override;
void init (const std::vector<std::string>& cmdArgs) override;
void terminate () override;
private:
enum OpenFlags
{
kSetComponentHandler = 1 << 0,
kSecondWindow = 1 << 1,
};
void openEditor (const std::string& path, VST3::Optional<VST3::UID> effectID, uint32 flags);
void createViewAndShow (IEditController* controller);
VST3::Hosting::Module::Ptr module {nullptr};
IPtr<PlugProvider> plugProvider {nullptr};
Vst::HostApplication pluginContext;
WindowPtr window;
std::shared_ptr<WindowController> windowController;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,40 @@
//------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/appinit.h
// Created by : Steinberg, 04/2005
// Description : Editor Host Example for VST SDK 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct AppInit
{
explicit AppInit (ApplicationPtr&& app)
{
IPlatform::instance ().setApplication (std::move (app));
}
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,44 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include <memory>
#include <string>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class IApplication
{
public:
virtual ~IApplication () noexcept = default;
virtual void init (const std::vector<std::string>& cmdArgs) = 0;
virtual void terminate () = 0;
};
using ApplicationPtr = std::unique_ptr<IApplication>;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,52 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include <functional>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class IPlatform
{
public:
virtual ~IPlatform () noexcept = default;
virtual void setApplication (ApplicationPtr&& app) = 0;
virtual WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) = 0;
virtual void quit () = 0;
virtual void kill (int resultCode, const std::string& reason) = 0;
virtual FUnknown* getPluginFactoryContext () = 0;
static IPlatform& instance ();
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
extern void ApplicationInit (int argc, const char* argv[]);
@@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/gui/iplugview.h"
#include <memory>
#include <string>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using Coord = int32_t;
//------------------------------------------------------------------------
struct Point
{
Coord x;
Coord y;
};
//------------------------------------------------------------------------
struct Size
{
Coord width;
Coord height;
};
//------------------------------------------------------------------------
inline bool operator!= (const Size& lhs, const Size& rhs)
{
return lhs.width != rhs.width || lhs.height != rhs.height;
}
//------------------------------------------------------------------------
inline bool operator== (const Size& lhs, const Size& rhs)
{
return lhs.width == rhs.width && lhs.height == rhs.height;
}
//------------------------------------------------------------------------
struct Rect
{
Point origin;
Size size;
};
//------------------------------------------------------------------------
inline Rect ViewRectToRect (ViewRect r)
{
Rect result {};
result.origin.x = r.left;
result.origin.y = r.top;
result.size.width = r.right - r.left;
result.size.height = r.bottom - r.top;
return result;
}
//------------------------------------------------------------------------
struct NativePlatformWindow
{
FIDString type;
void* ptr;
};
class IWindow;
//------------------------------------------------------------------------
class IWindowController
{
public:
virtual ~IWindowController () noexcept = default;
virtual void onShow (IWindow& window) = 0;
virtual void onClose (IWindow& window) = 0;
virtual void onResize (IWindow& window, Size newSize) = 0;
virtual void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) = 0;
virtual Size constrainSize (IWindow& window, Size requestedSize) = 0;
};
using WindowControllerPtr = std::shared_ptr<IWindowController>;
//------------------------------------------------------------------------
class IWindow
{
public:
virtual ~IWindow () noexcept = default;
virtual void show () = 0;
virtual void close () = 0;
virtual void resize (Size newSize) = 0;
virtual Size getContentSize () = 0;
virtual NativePlatformWindow getNativePlatformWindow () const = 0;
virtual tresult queryInterface (const TUID iid, void** obj) = 0;
};
using WindowPtr = std::shared_ptr<IWindow>;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,135 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h
// Created by : Steinberg 09.2023
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Steinberg Media Technologies nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iplugview.h"
#include <algorithm>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Linux {
//------------------------------------------------------------------------
class RunLoopImpl : U::ImplementsNonDestroyable<U::Directly<IRunLoop>>
{
public:
static IRunLoop& instance ()
{
static RunLoopImpl impl;
return impl;
}
tresult PLUGIN_API registerEventHandler (IEventHandler* handler, FileDescriptor fd) override;
tresult PLUGIN_API unregisterEventHandler (IEventHandler* handler) override;
tresult PLUGIN_API registerTimer (ITimerHandler* handler, TimerInterval milliseconds) override;
tresult PLUGIN_API unregisterTimer (ITimerHandler* handler) override;
private:
using TimerID = uint64_t;
using EventHandler = IPtr<Linux::IEventHandler>;
using TimerHandler = IPtr<Linux::ITimerHandler>;
using EventHandlers = std::unordered_map<Linux::FileDescriptor, EventHandler>;
using TimerHandlers = std::unordered_map<TimerID, TimerHandler>;
EventHandlers eventHandlers;
TimerHandlers timerHandlers;
};
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::registerEventHandler (IEventHandler* handler,
FileDescriptor fd)
{
if (!handler || eventHandlers.find (fd) != eventHandlers.end ())
return kInvalidArgument;
Vst::EditorHost::RunLoop::instance ().registerFileDescriptor (
fd, [handler] (int fd) { handler->onFDIsSet (fd); });
eventHandlers.emplace (fd, handler);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::unregisterEventHandler (IEventHandler* handler)
{
if (!handler)
return kInvalidArgument;
auto it = std::find_if (eventHandlers.begin (), eventHandlers.end (),
[&] (const auto& elem) { return elem.second == handler; });
if (it == eventHandlers.end ())
return kResultFalse;
Vst::EditorHost::RunLoop::instance ().unregisterFileDescriptor (it->first);
eventHandlers.erase (it);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::registerTimer (ITimerHandler* handler,
TimerInterval milliseconds)
{
if (!handler || milliseconds == 0)
return kInvalidArgument;
auto id = Vst::EditorHost::RunLoop::instance ().registerTimer (
milliseconds, [handler] (auto) { handler->onTimer (); });
timerHandlers.emplace (id, handler);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::unregisterTimer (ITimerHandler* handler)
{
if (!handler)
return kInvalidArgument;
auto it = std::find_if (timerHandlers.begin (), timerHandlers.end (),
[&] (const auto& elem) { return elem.second == handler; });
if (it == timerHandlers.end ())
return kResultFalse;
Vst::EditorHost::RunLoop::instance ().unregisterTimer (it->first);
timerHandlers.erase (it);
return kResultTrue;
}
//------------------------------------------------------------------------
} // Linux
} // Steinberg
@@ -0,0 +1,205 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/platform.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.h"
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using namespace std::chrono;
using clock = high_resolution_clock;
//------------------------------------------------------------------------
static int pause (int milliSeconds)
{
struct timeval timeOut;
if (milliSeconds > 0)
{
timeOut.tv_usec = (milliSeconds % (unsigned long)1000) * 1000;
timeOut.tv_sec = milliSeconds / (unsigned long)1000;
select (0, nullptr, nullptr, nullptr, &timeOut);
}
return (milliSeconds > 0 ? milliSeconds : 0);
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
class Platform : public IPlatform
{
public:
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
void run (const std::vector<std::string>& cmdArgs);
static const int kMinEventLoopRate = 16; // 60Hz
private:
void onWindowClosed (X11Window* window);
void closeAllWindows ();
void eventLoop ();
ApplicationPtr application;
Display* xDisplay {nullptr};
std::vector<std::shared_ptr<X11Window>> windows;
};
//------------------------------------------------------------------------
#if !SMTG_EDITOR_HOST_USE_WAYLAND
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
#endif
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = X11Window::make (title, size, resizeable, controller, xDisplay,
[this] (X11Window* window) { onWindowClosed (window); });
if (window)
windows.push_back (std::static_pointer_cast<X11Window> (window));
return window;
}
//------------------------------------------------------------------------
void Platform::onWindowClosed (X11Window* window)
{
for (auto it = windows.begin (); it != windows.end (); ++it)
{
if (it->get () == window)
{
windows.erase (it);
break;
}
}
}
//------------------------------------------------------------------------
void Platform::closeAllWindows ()
{
for (auto& w : windows)
{
w->close ();
}
}
//------------------------------------------------------------------------
void Platform::quit ()
{
static bool recursiveGuard = false;
if (recursiveGuard)
return;
recursiveGuard = true;
closeAllWindows ();
if (application)
application->terminate ();
RunLoop::instance ().stop ();
recursiveGuard = false;
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return &Steinberg::Linux::RunLoopImpl::instance ();
}
//------------------------------------------------------------------------
void Platform::run (const std::vector<std::string>& cmdArgs)
{
// Connect to X server
std::string displayName (getenv ("DISPLAY"));
if (displayName.empty ())
displayName = ":0.0";
if ((xDisplay = XOpenDisplay (displayName.data ())) == nullptr)
{
return;
}
RunLoop::instance ().setDisplay (xDisplay);
application->init (cmdArgs);
eventLoop ();
XCloseDisplay (xDisplay);
}
//------------------------------------------------------------------------
void Platform::eventLoop ()
{
RunLoop::instance ().start ();
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#if !SMTG_EDITOR_HOST_USE_WAYLAND
int main (int argc, char* argv[])
{
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
@@ -0,0 +1,280 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
#include <algorithm>
#include <iostream>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using LockGuard = std::lock_guard<std::recursive_mutex>;
//------------------------------------------------------------------------
RunLoop& RunLoop::instance ()
{
static RunLoop gInstance;
return gInstance;
}
//------------------------------------------------------------------------
void RunLoop::setDisplay (Display* display)
{
this->display = display;
}
//------------------------------------------------------------------------
void RunLoop::registerWindow (XID window, const EventCallback& callback)
{
map.emplace (window, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterWindow (XID window)
{
auto it = map.find (window);
if (it == map.end ())
return;
map.erase (it);
}
//------------------------------------------------------------------------
void RunLoop::registerFileDescriptor (int fd, const FileDescriptorCallback& callback)
{
fileDescriptors.emplace (fd, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterFileDescriptor (int fd)
{
auto it = fileDescriptors.find (fd);
if (it == fileDescriptors.end ())
return;
fileDescriptors.erase (it);
}
//------------------------------------------------------------------------
void RunLoop::select (timeval* timeout)
{
int nfds = 0;
fd_set readFDs = {}, writeFDs = {}, exceptFDs = {};
for (auto& e : fileDescriptors)
{
int fd = e.first;
FD_SET (fd, &readFDs);
FD_SET (fd, &writeFDs);
FD_SET (fd, &exceptFDs);
nfds = std::max (nfds, fd);
}
int result = ::select (nfds, &readFDs, &writeFDs, nullptr, timeout);
if (result > 0)
{
for (auto& e : fileDescriptors)
{
if (FD_ISSET (e.first, &readFDs) || FD_ISSET (e.first, &writeFDs) ||
FD_ISSET (e.first, &exceptFDs))
e.second (e.first);
}
}
}
//------------------------------------------------------------------------
bool RunLoop::handleEvents ()
{
auto count = XPending (display);
if (count == 0)
return false;
for (auto i = 0; i < count; ++i)
{
XEvent event {};
XNextEvent (display, &event);
auto it = map.find (event.xany.window);
if (it != map.end ())
{
it->second (event);
if (event.type == DestroyNotify)
{
map.erase (it);
}
}
else
{
XPutBackEvent (display, &event);
break;
}
}
return true;
}
//------------------------------------------------------------------------
TimerID RunLoop::registerTimer (TimerInterval interval, const TimerCallback& callback)
{
return timerProcessor.registerTimer (interval, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterTimer (TimerID id)
{
timerProcessor.unregisterTimer (id);
}
//------------------------------------------------------------------------
bool timeValEmpty (timeval& val)
{
return val.tv_sec == 0 && val.tv_usec == 0;
}
//------------------------------------------------------------------------
void RunLoop::start ()
{
using namespace std::chrono;
using clock = high_resolution_clock;
running = true;
auto fd = XConnectionNumber (display);
registerFileDescriptor (fd, [this] (int) { handleEvents (); });
XSync (display, false);
handleEvents ();
timeval selectTimeout {};
while (running && !map.empty ())
{
select (timeValEmpty (selectTimeout) ? nullptr : &selectTimeout);
auto nextFireTime = timerProcessor.handleTimersAndReturnNextFireTimeInMs ();
if (nextFireTime == TimerProcessor::noTimers)
{
selectTimeout = {};
}
else
{
selectTimeout.tv_sec = nextFireTime / 1000;
selectTimeout.tv_usec = (nextFireTime - (selectTimeout.tv_sec * 1000)) * 1000;
}
}
}
//------------------------------------------------------------------------
void RunLoop::stop ()
{
running = false;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
uint64_t TimerProcessor::handleTimersAndReturnNextFireTimeInMs ()
{
using std::chrono::time_point_cast;
if (timers.empty ())
return noTimers;
auto current = time_point_cast<Millisecond> (Clock::now ());
std::vector<TimerID> timersToFire;
for (auto& timer : timers)
{
if (timer.nextFireTime > current)
break;
timersToFire.push_back (timer.id);
updateTimerNextFireTime (timer, current);
}
for (auto id : timersToFire)
{
for (auto& timer : timers)
{
if (timer.id == id)
{
timer.callback (timer.id);
break;
}
}
}
if (timersToFire.empty ())
return noTimers;
sortTimers ();
auto nextFireTime = timers.front ().nextFireTime;
current = now ();
if (nextFireTime < current)
return 0;
return (nextFireTime - current).count ();
}
//------------------------------------------------------------------------
void TimerProcessor::updateTimerNextFireTime (Timer& timer, TimePoint current)
{
timer.nextFireTime = current + Millisecond (timer.interval);
}
//------------------------------------------------------------------------
void TimerProcessor::sortTimers ()
{
std::sort (timers.begin (), timers.end (),
[] (const Timer& t1, const Timer& t2) { return t1.nextFireTime < t2.nextFireTime; });
}
//------------------------------------------------------------------------
auto TimerProcessor::now () -> TimePoint
{
using std::chrono::time_point_cast;
return time_point_cast<Millisecond> (Clock::now ());
}
//------------------------------------------------------------------------
auto TimerProcessor::registerTimer (TimerInterval interval, const TimerCallback& callback)
-> TimerID
{
auto timerId = ++timerIdCounter;
Timer timer;
timer.id = timerId;
timer.callback = callback;
timer.interval = interval;
updateTimerNextFireTime (timer, now ());
timers.emplace_back (std::move (timer));
sortTimers ();
return timerId;
}
//------------------------------------------------------------------------
void TimerProcessor::unregisterTimer (TimerID id)
{
for (auto it = timers.begin (), end = timers.end (); it != end; ++it)
{
if (it->id == id)
{
timers.erase (it);
break;
}
}
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,110 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include <X11/Xlib.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
using TimerID = uint64_t;
using TimerInterval = uint64_t;
using TimerCallback = std::function<void (TimerID)>;
//------------------------------------------------------------------------
class TimerProcessor
{
public:
TimerID registerTimer (TimerInterval interval, const TimerCallback& callback);
void unregisterTimer (TimerID id);
static constexpr uint64_t noTimers = std::numeric_limits<uint64_t>::max ();
uint64_t handleTimersAndReturnNextFireTimeInMs ();
private:
using Clock = std::chrono::steady_clock;
using Millisecond = std::chrono::milliseconds;
using TimePoint = std::chrono::time_point<Clock, Millisecond>;
struct Timer
{
TimerID id;
TimerInterval interval;
TimerCallback callback;
TimePoint nextFireTime;
};
using Timers = std::vector<Timer>;
Timers timers;
TimerID timerIdCounter {0};
void updateTimerNextFireTime (Timer& timer, TimePoint current);
void sortTimers ();
TimePoint now ();
};
//------------------------------------------------------------------------
class RunLoop
{
public:
using EventCallback = std::function<bool (const XEvent& event)>;
using FileDescriptorCallback = std::function<void (int fd)>;
static RunLoop& instance ();
void setDisplay (Display* display);
void registerWindow (XID window, const EventCallback& callback);
void unregisterWindow (XID window);
void registerFileDescriptor (int fd, const FileDescriptorCallback& callback);
void unregisterFileDescriptor (int fd);
TimerID registerTimer (TimerInterval interval, const TimerCallback& callback);
void unregisterTimer (TimerID id);
void start ();
void stop ();
private:
void select (timeval* timeout = nullptr);
bool handleEvents ();
using WindowMap = std::unordered_map<XID, EventCallback>;
using FileDescriptorCallbacks = std::unordered_map<int, FileDescriptorCallback>;
WindowMap map;
FileDescriptorCallbacks fileDescriptors;
TimerProcessor timerProcessor;
Display* display {nullptr};
bool running {false};
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,493 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/clientcontext.cpp
// Created by : Steinberg 10.2025
// Description : Implementation of WaylandServerDelegate::IWaylandClientContext
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "clientcontext.h"
#include <algorithm>
#include <string>
#include <unordered_map>
#include <vector>
// Wayland globals
#include "linux-dmabuf-v1-client-protocol.h"
#include "wayland-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
using namespace WaylandServerDelegate;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
using ContextListeners = std::vector<IContextListener*>;
using NamedWaylandOutput = std::pair<uint32_t, WaylandOutput>;
using WaylandOutputs = std::vector<NamedWaylandOutput>;
using StringType = std::string;
//------------------------------------------------------------------------
// WaylandGlobals
// https://wayland.freedesktop.org/docs/html/
// https://wayland-book.com/introduction.html
//------------------------------------------------------------------------
struct WaylandGlobals
{
using WlInterfaceName = std::string;
using Globals = std::unordered_map<uint32_t, WlInterfaceName>;
wl_compositor* compositor {nullptr};
wl_seat* seat {nullptr};
wl_shm* shm {nullptr};
wl_subcompositor* subcompositor {nullptr};
xdg_wm_base* wm_base {nullptr};
zwp_linux_dmabuf_v1* dmabuf {nullptr};
Globals objects; // Hold a map of all globals' interface names for better tracking!
};
//------------------------------------------------------------------------
static void handlePing (void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
{
auto globals = reinterpret_cast<WaylandGlobals*> (data);
xdg_wm_base_pong (globals->wm_base, serial);
}
//------------------------------------------------------------------------
static void addXdgWmBaseListener (WaylandGlobals& globals)
{
static const struct xdg_wm_base_listener xdgWmBaseListener = {
.ping = handlePing,
};
xdg_wm_base_add_listener (globals.wm_base, &xdgWmBaseListener, &globals);
}
//------------------------------------------------------------------------
static void handleGlobal (void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version)
{
printf ("interface: '%s', version: %d, name: %d\n", interface, version, name);
auto& globals = *reinterpret_cast<WaylandGlobals*> (data);
globals.objects.insert ({name, {interface}});
if (std::string_view (interface) == wl_compositor_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object = wl_registry_bind (registry, name, &wl_compositor_interface,
std::min (kVersion, version));
auto compositor = reinterpret_cast<wl_compositor*> (object);
globals.compositor = compositor;
return;
}
if (std::string_view (interface) == wl_subcompositor_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object = wl_registry_bind (registry, name, &wl_subcompositor_interface,
std::min (kVersion, version));
auto subcompositor = reinterpret_cast<wl_subcompositor*> (object);
globals.subcompositor = subcompositor;
return;
}
if (std::string_view (interface) == wl_shm_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object =
wl_registry_bind (registry, name, &wl_shm_interface, std::min (kVersion, version));
auto shm = reinterpret_cast<wl_shm*> (object);
globals.shm = shm;
return;
}
if (std::string_view (interface) == xdg_wm_base_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object =
wl_registry_bind (registry, name, &xdg_wm_base_interface, std::min (kVersion, version));
auto wm_base = reinterpret_cast<xdg_wm_base*> (object);
globals.wm_base = wm_base;
return;
}
if (std::string_view (interface) == wl_seat_interface.name)
{
static constexpr uint32_t kVersion = 8;
void* object =
wl_registry_bind (registry, name, &wl_seat_interface, std::min (kVersion, version));
auto seat = reinterpret_cast<wl_seat*> (object);
globals.seat = seat;
return;
}
if (std::string_view (interface) == wl_output_interface.name)
{
// We bind outputs in the WaylandClientContext
/*
static constexpr uint32_t kVersion = 3;
void* object =
wl_registry_bind (registry, name, &wl_output_interface, std::min (kVersion, version));
auto output = reinterpret_cast<wl_output*> (object);
return;
*/
}
if (std::string_view (interface) == zwp_linux_dmabuf_v1_interface.name)
{
static constexpr uint32_t kVersion = 4;
void* object = wl_registry_bind (registry, name, &zwp_linux_dmabuf_v1_interface,
std::min (kVersion, version));
auto dmabuf = reinterpret_cast<zwp_linux_dmabuf_v1*> (object);
globals.dmabuf = dmabuf;
return;
}
}
//------------------------------------------------------------------------
static void handleGlobalRemove (void* data, struct wl_registry* registry, uint32_t name)
{
auto globals = reinterpret_cast<WaylandGlobals*> (data);
const auto iter = globals->objects.find (name);
if (iter == globals->objects.end ())
return;
globals->objects.erase (iter);
}
//------------------------------------------------------------------------
static bool bindGlobals (wl_display* display, WaylandGlobals& globals)
{
if (!display)
return false;
static const struct wl_registry_listener registry_listener = {
.global = handleGlobal, .global_remove = handleGlobalRemove,
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, &globals);
// Roundtrip to call the listener's callbacks.
wl_display_roundtrip (display);
addXdgWmBaseListener (globals);
return true;
}
//------------------------------------------------------------------------
// WaylandClientContext::Impl
//------------------------------------------------------------------------
struct WaylandClientContext::Impl
{
wl_display* display = nullptr;
WaylandGlobals globals;
WaylandOutputs outputs;
ContextListeners contextListeners;
uint32_t seatCapabilities = 0;
StringType seatName;
void setSeatCapabilities (int capabilities);
void notifyListeners (IContextListener::ChangeType changeType);
void addWaylandOutput (uint32_t name, wl_output* out);
void removeWaylandOutput (uint32_t name);
void addRegistryListener ();
void addSeatListener ();
void addOutputsListener ();
};
//------------------------------------------------------------------------
void WaylandClientContext::Impl::setSeatCapabilities (int capabilities)
{
seatCapabilities = capabilities;
// seatCapabilities &= ~(WL_SEAT_CAPABILITY_KEYBOARD);
notifyListeners (IContextListener::kSeatCapabilitiesChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::notifyListeners (IContextListener::ChangeType changeType)
{
for (auto& el : contextListeners)
el->contextChanged (changeType);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addWaylandOutput (uint32_t name, wl_output* output)
{
outputs.push_back ({name, {output}});
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::removeWaylandOutput (uint32_t name)
{
auto iter = std::find_if (outputs.begin (), outputs.end (),
[name] (const auto& el) { return el.first == name; });
if (iter == outputs.end ())
return;
outputs.erase (iter);
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addRegistryListener ()
{
static const struct wl_registry_listener registry_listener = {
.global =
[] (void* data, struct wl_registry* registry, uint32_t name, const char* interface,
uint32_t version) {
// We only track outputs here for now
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
if (std::string_view (interface) == wl_output_interface.name)
{
static constexpr uint32_t kVersion = 3;
void* object = wl_registry_bind (registry, name, &wl_output_interface,
std::min (kVersion, version));
auto output = reinterpret_cast<wl_output*> (object);
self->addWaylandOutput (name, output);
return;
}
},
.global_remove =
[] (void* data, struct wl_registry* registry, uint32_t name) {
// We only track outputs here for now
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->removeWaylandOutput (name);
},
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addSeatListener ()
{
static const struct wl_seat_listener listener = {
.capabilities =
[] (void* data, struct wl_seat* wl_seat, uint32_t capabilities) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->setSeatCapabilities (capabilities);
},
.name =
[] (void* data, struct wl_seat* wl_seat, const char* name) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
if (name)
self->seatName = name;
}};
wl_seat_add_listener (globals.seat, &listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addOutputsListener ()
{
static const wl_output_listener listener = {
.geometry =
[] (void* data, wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel, const char* make, const char* model,
int32_t transform) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
WaylandOutput& output = iter->second;
output.handle = wl_output;
output.x = x;
output.y = y;
output.physicalWidth = physical_width;
output.physicalHeight = physical_height;
output.subPixelOrientation = subpixel;
output.transformType = transform;
const std::string_view makeStr {make, sizeof (output.manufacturer)};
std::copy (makeStr.begin (), makeStr.end (), output.manufacturer);
const std::string_view modelStr {model, sizeof (output.model)};
std::copy (modelStr.begin (), modelStr.end (), output.model);
}
},
.mode =
[] (void* data, wl_output* wl_output, uint32_t flags, int32_t width, int32_t height,
int32_t refresh) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.width = width;
output.height = height;
output.refreshRate = refresh;
}
},
.done =
[] (void* data, wl_output* wl_output) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->notifyListeners (IContextListener::kOutputsChanged);
},
.scale =
[] (void* data, wl_output* wl_output, int32_t factor) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.scaleFactor = factor;
}
},
.name =
[] (void* data, wl_output* wl_output, const char* name) {
// Hmm, was never called.
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
},
.description =
[] (void* data, wl_output* wl_output, const char* description) {
// Hmm, was never called.
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
},
};
for (auto& output : outputs)
wl_output_add_listener (output.second.handle, &listener, this);
}
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
WaylandClientContext::WaylandClientContext ()
{
impl = std::make_unique<Impl> ();
}
//------------------------------------------------------------------------
WaylandClientContext::~WaylandClientContext ()
{
}
//------------------------------------------------------------------------
bool WaylandClientContext::initWayland (wl_display* display)
{
impl->display = display;
bool done = bindGlobals (display, impl->globals);
impl->addRegistryListener ();
impl->addSeatListener ();
// Initializes seat capabilities, outputs etc.
wl_display_roundtrip (impl->display);
impl->addOutputsListener ();
return done;
}
//------------------------------------------------------------------------
bool WaylandClientContext::addListener (IContextListener* listener)
{
if (!listener)
return false;
impl->contextListeners.push_back (listener);
return true;
}
//------------------------------------------------------------------------
bool WaylandClientContext::removeListener (IContextListener* listener)
{
const auto found = [listener] (const auto el) { return el == listener; };
auto iter =
std::find_if (impl->contextListeners.begin (), impl->contextListeners.end (), found);
if (iter == impl->contextListeners.end ())
return false;
impl->contextListeners.erase (iter);
return true;
}
//------------------------------------------------------------------------
wl_compositor* WaylandClientContext::getCompositor () const
{
return impl->globals.compositor;
}
//------------------------------------------------------------------------
wl_subcompositor* WaylandClientContext::getSubCompositor () const
{
return impl->globals.subcompositor;
}
//------------------------------------------------------------------------
wl_shm* WaylandClientContext::getSharedMemory () const
{
return impl->globals.shm;
}
//------------------------------------------------------------------------
wl_seat* WaylandClientContext::getSeat () const
{
return impl->globals.seat;
}
//------------------------------------------------------------------------
xdg_wm_base* WaylandClientContext::getWindowManager () const
{
return impl->globals.wm_base;
}
//------------------------------------------------------------------------
uint32_t WaylandClientContext::getSeatCapabilities () const
{
return impl->seatCapabilities;
}
//------------------------------------------------------------------------
const char* WaylandClientContext::getSeatName () const
{
return impl->seatName.data ();
}
//------------------------------------------------------------------------
int WaylandClientContext::countOutputs () const
{
return impl->outputs.size ();
}
//------------------------------------------------------------------------
zwp_linux_dmabuf_v1* WaylandClientContext::getDmaBuffer () const
{
return impl->globals.dmabuf;
}
//------------------------------------------------------------------------
const WaylandOutput& WaylandClientContext::getOutput (int index) const
{
if (index < impl->outputs.size ())
return impl->outputs[index].second;
static WaylandOutput output {};
return output;
}
//------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/clientcontext.h
// Created by : Steinberg 10.2025
// Description : Implementation of WaylandServerDelegate::IWaylandClientContext
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include <memory>
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
class WaylandClientContext final : public WaylandServerDelegate::IWaylandClientContext
{
public:
//------------------------------------------------------------------------
// WaylandServerDelegate
using WaylandOutput = WaylandServerDelegate::WaylandOutput;
using IContextListener = WaylandServerDelegate::IContextListener;
WaylandClientContext ();
~WaylandClientContext ();
bool initWayland (wl_display* display);
// IWaylandClientContext
bool addListener (IContextListener* listener) override;
bool removeListener (IContextListener* listener) override;
wl_compositor* getCompositor () const override;
wl_subcompositor* getSubCompositor () const override;
wl_shm* getSharedMemory () const override;
wl_seat* getSeat () const override;
xdg_wm_base* getWindowManager () const override;
uint32_t getSeatCapabilities () const override;
const char* getSeatName () const override;
int countOutputs () const override;
zwp_linux_dmabuf_v1* getDmaBuffer () const override;
const WaylandOutput& getOutput (int index) const override;
//------------------------------------------------------------------------
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,375 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/platform.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "../../../editorhost.h"
#include "../../iplatform.h"
#include "clientcontext.h"
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include "wayland-server-delegate/iwaylandserver.h"
#include "wayland-server-delegate/waylandresource.h"
#include "window.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iwaylandframe.h"
#include <array>
#include <cassert>
#include <fcntl.h>
#include <iostream>
#include <mutex>
#include <optional>
#include <poll.h>
#include <string_view>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <wayland-server.h>
namespace Steinberg::Vst::EditorHost {
namespace Wayland {
//------------------------------------------------------------------------
struct ReleaseWaylandDisplay
{
void operator () (wl_display* d) const { wl_display_disconnect (d); }
};
using WaylandDisplayPtr = std::unique_ptr<wl_display, ReleaseWaylandDisplay>;
//------------------------------------------------------------------------
static int createCancelSockets (int (&fds)[2])
{
if (::socketpair (AF_UNIX, SOCK_STREAM, 0, fds) == -1)
return -1;
::fcntl (fds[0], F_SETFD, FD_CLOEXEC);
return 0;
}
//------------------------------------------------------------------------
static void closeCancelSockets (int (&fds)[2])
{
int msg = 0;
ssize_t bytesWritten = write (fds[0], &msg, sizeof (msg));
::close (fds[0]);
::close (fds[1]);
}
//------------------------------------------------------------------------
// Threading
//------------------------------------------------------------------------
namespace Threading {
using Mutex = std::mutex;
using ScopedLock = const std::lock_guard<std::mutex>;
using Thread = std::thread;
} // namespace Threading
//------------------------------------------------------------------------
// Platform
//------------------------------------------------------------------------
class Platform
: public IPlatform,
public U::ImplementsNonDestroyable<U::Directly<IWaylandHost, Linux::IEventHandler>>,
public IWaylandProxyCreator
{
public:
//------------------------------------------------------------------------
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void run (const std::vector<std::string>& cmdArgs);
//------------------------------------------------------------------------
private:
using WaylandWindows = std::vector<std::shared_ptr<WaylandWindow>>;
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
void initWayland ();
wl_display* getDisplay () const { return displayPtr.get (); }
// IWaylandHost
wl_display* PLUGIN_API openWaylandConnection () override;
tresult PLUGIN_API closeWaylandConnection (wl_display* display) override;
// IWaylandProxyCreator
wl_proxy* createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation) override;
void destroyProxy (wl_proxy* proxy) override;
// Linux::IEventHandler
void PLUGIN_API onFDIsSet (Linux::FileDescriptor fd) override;
WaylandClientContext client;
ApplicationPtr application;
WaylandDisplayPtr displayPtr;
wl_event_queue* eventQueue {nullptr};
WaylandWindows windows;
IPtr<RunLoop> runLoop;
bool isQuitting = false;
bool shouldTerminate () { return isQuitting; }
Threading::Mutex lock;
int threadEntry ();
int serverFd {};
int cancelFd[2];
};
//------------------------------------------------------------------------
void Platform::initWayland ()
{
displayPtr = WaylandDisplayPtr (wl_display_connect (nullptr));
if (!getDisplay ())
kill (-1, "wl_display_connect failed");
bool initWaylandDone = client.initWayland (getDisplay ());
if (!initWaylandDone)
kill (-1, "init wayland failed");
eventQueue = wl_display_create_queue (getDisplay ());
serverFd = WaylandServerDelegate::IWaylandServer::instance ().startup (&client, eventQueue);
if (serverFd == -1)
kill (-1, "IWaylandServer startup failed");
wl_display_roundtrip (getDisplay ());
wl_display_flush (getDisplay ());
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return &Steinberg::Linux::RunLoopImpl::instance ();
}
//------------------------------------------------------------------------
void Platform::run (const std::vector<std::string>& cmdArgs)
{
initWayland ();
runLoop = owned (new RunLoop (getDisplay ()));
// start server dispatch before initializing the application, otherwise plug-ins might block the
// main thread
createCancelSockets (cancelFd);
Threading::Thread serverThread (&Platform::threadEntry, this);
application->init (cmdArgs);
runLoop->run ();
closeCancelSockets (cancelFd);
serverThread.join ();
}
//------------------------------------------------------------------------
void PLUGIN_API Platform::onFDIsSet (Linux::FileDescriptor fd)
{
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().dispatch ();
}
}
//------------------------------------------------------------------------
#define ARRAY_COUNT(arr) (std::end (arr) - std::begin (arr))
int Platform::threadEntry ()
{
wl_display* display = getDisplay ();
wl_event_queue* queue = eventQueue;
int displayFd = wl_display_get_fd (display);
bool receivedClientEvents = false;
while (true)
{
// flush server events (server -> clients)
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().flush ();
}
// flush display (server -> session compositor)
if (receivedClientEvents)
wl_display_roundtrip_queue (display, queue);
else
wl_display_flush (display);
receivedClientEvents = false;
if (wl_display_prepare_read_queue (display, queue) == 0)
{
pollfd fds[] = {
{serverFd, POLLIN, 0}, {displayFd, POLLIN, 0}, {cancelFd[0], POLLIN, 0}};
::poll (fds, ARRAY_COUNT (fds), -1);
// dispatch incoming server events (clients -> server)
if ((fds[0].revents & POLLIN) > 0)
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().dispatch ();
receivedClientEvents = true;
}
// dispatch server-side Wayland objects (session compositor -> server)
if ((fds[1].revents & POLLIN) > 0)
{
if (wl_display_read_events (display) < 0)
{
// CCL_WARN ("%s: %s\n", "Failed to read server Wayland events", ::strerror
// (errno))
break;
}
}
else
{
wl_display_cancel_read (display);
}
}
else
{
// CCL_PRINTLN ("WaylandServerRunLoop: Dispatching pending server-side Wayland objects
// (session compositor -> server)")
if (wl_display_dispatch_queue_pending (display, queue) < 0)
{
// CCL_WARN ("%s: %s\n", "Failed to dispatch pending Wayland server events to
// display", ::strerror (errno))
break;
}
}
if (shouldTerminate ())
break;
}
return 0;
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& obj)
{
application = std::move (obj);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = WaylandWindow::make (
title, size, resizeable, controller,
[this] (WaylandWindow* window) {
auto it = std::find_if (windows.begin (), windows.end (),
[&] (const auto& el) { return el.get () == window; });
if (it != windows.end ())
windows.erase (it);
},
this, runLoop, this, client);
windows.push_back (window);
return window;
}
//------------------------------------------------------------------------
void Platform::quit ()
{
assert (getDisplay ());
isQuitting = true;
runLoop->stop ();
if (application)
application->terminate ();
WaylandServerDelegate::IWaylandServer::instance ().shutdown ();
wl_event_queue_destroy (eventQueue);
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
exit (resultCode);
}
//------------------------------------------------------------------------
wl_display* PLUGIN_API Platform::openWaylandConnection ()
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().openClientConnection ();
}
//------------------------------------------------------------------------
tresult PLUGIN_API Platform::closeWaylandConnection (wl_display* display)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().closeClientConnection (display) ?
kResultTrue :
kResultFalse;
}
//------------------------------------------------------------------------
wl_proxy* Platform::createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().createProxy (display, object,
implementation);
}
//------------------------------------------------------------------------
void Platform::destroyProxy (wl_proxy* proxy)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().destroyProxy (proxy);
}
//------------------------------------------------------------------------
} // Wayland
//------------------------------------------------------------------------
#if SMTG_EDITOR_HOST_USE_WAYLAND
IPlatform& IPlatform::instance ()
{
return Wayland::Platform::instance ();
}
#endif
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#if SMTG_EDITOR_HOST_USE_WAYLAND
int main (int argc, char* argv[])
{
#ifdef EDITORHOST_GTK
gtk_init (&argc, &argv);
#endif
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Wayland::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
@@ -0,0 +1,285 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/runloop.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "runloop.h"
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <memory>
#include <poll.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <vector>
#include <wayland-client.h>
#include <wayland-server.h>
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
static uint64_t toSeconds (uint64_t milliseconds)
{
return milliseconds / 1000;
}
//------------------------------------------------------------------------
static uint64_t toNanoSecs (uint64_t milliseconds)
{
return milliseconds * 1000000;
}
//------------------------------------------------------------------------
static auto interval_timerfd_create (Linux::TimerInterval milliseconds) -> int
{
auto timer_fd = timerfd_create (CLOCK_REALTIME, 0);
if (timer_fd == -1)
{
std::cerr << "Failed to create a new timer." << std::endl;
return timer_fd;
}
const auto secs = toSeconds (milliseconds);
const auto nanoSecs = toNanoSecs (milliseconds % 1000);
struct itimerspec timerValue;
timerValue.it_value.tv_sec = secs;
timerValue.it_value.tv_nsec = nanoSecs;
timerValue.it_interval.tv_sec = secs;
timerValue.it_interval.tv_nsec = nanoSecs;
if (timerfd_settime (timer_fd, 0, &timerValue, NULL) == -1)
{
std::cerr << "Failed to set a timer specification." << std::endl;
return -1;
}
return timer_fd;
}
//------------------------------------------------------------------------
// IntervalTimer
//------------------------------------------------------------------------
struct IntervalTimer : std::enable_shared_from_this<IntervalTimer>
{
using ITimerHandler = Linux::ITimerHandler;
IntervalTimer (ITimerHandler* handler) : handler (handler) {}
auto onFDIsSet () -> void
{
if (handler)
handler->onTimer ();
}
ITimerHandler* getHandler () const { return handler; }
private:
IPtr<ITimerHandler> handler;
};
//------------------------------------------------------------------------
// RunLoop::Impl
//------------------------------------------------------------------------
struct RunLoop::Impl
{
using FileDescriptors = std::vector<int>;
using Timers = std::vector<std::pair<std::shared_ptr<IntervalTimer>, FileDescriptor>>;
using EventHandlers = std::vector<std::pair<IPtr<IEventHandler>, FileDescriptor>>;
using PollRequests = std::vector<pollfd>;
wl_display* display {nullptr};
Timers timers;
EventHandlers eventHandlers;
PollRequests pfds;
bool running {false};
static auto collectPollRequests (const FileDescriptors& fds, const EventHandlers& handlers,
const Timers& timers, PollRequests& pfds) -> void;
static auto dispatchPolledEvents (const PollRequests& pfds, EventHandlers& handlers,
Timers& timers) -> void;
void run ();
void stop () { running = false; }
};
//------------------------------------------------------------------------
// RunLoop::Impl
//------------------------------------------------------------------------
auto RunLoop::Impl::collectPollRequests (const FileDescriptors& fds, const EventHandlers& handlers,
const Timers& timers, PollRequests& pfds) -> void
{
pfds.clear ();
for (const auto& fd : fds)
{
pfds.push_back ({/* .fd = */ fd,
/* .events = */ POLLIN,
/* .revents = */ 0});
}
for (const auto& handler : handlers)
{
pfds.push_back ({/* .fd = */ handler.second,
/* .events = */ POLLIN,
/* .revents = */ 0});
};
for (const auto& timer : timers)
{
pfds.push_back ({/* .fd = */ timer.second,
/* .events = */ POLLIN,
/* .revents = */ 0});
};
}
//------------------------------------------------------------------------
auto RunLoop::Impl::dispatchPolledEvents (const PollRequests& pfds, EventHandlers& handlers,
Timers& timers) -> void
{
for (const auto& pfd : pfds)
{
auto found = [&] (const auto& el) { return el.second == pfd.fd; };
if (pfd.revents & POLLIN)
{
auto it = std::find_if (handlers.begin (), handlers.end (), found);
if (it != handlers.end ())
{
it->first->onFDIsSet (it->second);
continue;
}
auto iter = std::find_if (timers.begin (), timers.end (), found);
if (iter != timers.end ())
{
uint64_t exp;
const int timer_fd = pfd.fd;
// 'read' must be called to clear the file descriptor's events
ssize_t s = read (timer_fd, &exp, sizeof (uint64_t));
iter->first->onFDIsSet ();
}
}
}
}
//------------------------------------------------------------------------
void RunLoop::Impl::run ()
{
running = true;
const auto displayFd = wl_display_get_fd (display);
const FileDescriptors fds = {displayFd};
while (running)
{
collectPollRequests (fds, eventHandlers, timers, pfds);
while (wl_display_prepare_read (display) != 0)
wl_display_dispatch_pending (display);
wl_display_flush (display);
// -1: 'poll' blocks until one of the provided polling requests has events that occurred
const auto res = poll (pfds.data (), pfds.size (), -1);
// Afterwards read all events for the display, if there are any
if (res == -1 || pfds[0].revents == 0)
wl_display_cancel_read (display);
else if (pfds[0].revents & POLLIN)
wl_display_read_events (display);
dispatchPolledEvents (pfds, eventHandlers, timers);
}
}
//------------------------------------------------------------------------
// RunLoop
//------------------------------------------------------------------------
RunLoop::RunLoop (wl_display* display)
{
impl = std::make_unique<Impl> ();
impl->display = display;
}
//------------------------------------------------------------------------
RunLoop::~RunLoop () noexcept = default;
//------------------------------------------------------------------------
void RunLoop::run ()
{
impl->run ();
}
//------------------------------------------------------------------------
void RunLoop::stop ()
{
impl->stop ();
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::registerEventHandler (IEventHandler* handler, FileDescriptor fd)
{
if (!handler)
return kInvalidArgument;
impl->eventHandlers.push_back ({handler, fd});
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::unregisterEventHandler (IEventHandler* handler)
{
auto it = std::find_if (impl->eventHandlers.begin (), impl->eventHandlers.end (),
[&] (const auto& el) { return el.first == handler; });
if (it != impl->eventHandlers.end ())
{
impl->eventHandlers.erase (it);
return kResultTrue;
}
return kInvalidArgument;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::registerTimer (ITimerHandler* handler, TimerInterval milliseconds)
{
auto src = std::make_shared<IntervalTimer> (handler);
auto timer_fd = interval_timerfd_create (milliseconds);
if (timer_fd == -1)
return kInternalError;
impl->timers.push_back (std::make_pair (src, timer_fd));
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::unregisterTimer (ITimerHandler* handler)
{
auto it = std::find_if (impl->timers.begin (), impl->timers.end (),
[&] (const auto& el) { return el.first->getHandler () == handler; });
if (it != impl->timers.end ())
{
const auto timer_fd = it->second;
::close (timer_fd);
impl->timers.erase (it);
return kResultTrue;
}
return kResultFalse;
}
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/runloop.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iplugview.h"
#include <memory>
struct wl_display;
struct wl_event_loop;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// RunLoop
//------------------------------------------------------------------------
struct RunLoop : U::Implements<U::Directly<Linux::IRunLoop>>
{
using IEventHandler = Linux::IEventHandler;
using FileDescriptor = Linux::FileDescriptor;
using TimerInterval = Linux::TimerInterval;
using ITimerHandler = Linux::ITimerHandler;
// IRunLoop
tresult PLUGIN_API registerEventHandler (IEventHandler* handler, FileDescriptor fd) override;
tresult PLUGIN_API unregisterEventHandler (IEventHandler* handler) override;
tresult PLUGIN_API registerTimer (ITimerHandler* handler, TimerInterval milliseconds) override;
tresult PLUGIN_API unregisterTimer (ITimerHandler* handler) override;
RunLoop (wl_display* display);
~RunLoop () noexcept override;
void run ();
void stop ();
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,256 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/surfacedelegate.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "surfacedelegate.h"
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// SurfaceDelegate
//------------------------------------------------------------------------
SurfaceDelegate::SurfaceDelegate ()
: WaylandServerDelegate::WaylandResource (&::wl_surface_interface,
static_cast<wl_surface_interface*> (this))
{
wl_surface_interface::destroy = destroy;
wl_surface_interface::attach = attach;
wl_surface_interface::damage = damage;
wl_surface_interface::frame = frame;
wl_surface_interface::set_opaque_region = set_opaque_region;
wl_surface_interface::set_input_region = set_input_region;
wl_surface_interface::commit = commit;
wl_surface_interface::set_buffer_transform = set_buffer_transform;
wl_surface_interface::set_buffer_scale = set_buffer_scale;
wl_surface_interface::damage_buffer = damage_buffer;
wl_surface_interface::offset = offset;
}
//------------------------------------------------------------------------
void SurfaceDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void SurfaceDelegate::SurfaceDelegate::attach (struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* buffer, int32_t x, int32_t y)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::damage (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::frame (struct wl_client* client, struct wl_resource* resource,
uint32_t callback)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_opaque_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_input_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::commit (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_buffer_transform (struct wl_client* client, struct wl_resource* resource,
int32_t transform)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_buffer_scale (struct wl_client* client, struct wl_resource* resource,
int32_t scale)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::damage_buffer (struct wl_client* client, struct wl_resource* resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::offset (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y)
{
}
//------------------------------------------------------------------------
// XdgSurfaceDelegate
//------------------------------------------------------------------------
XdgSurfaceDelegate::XdgSurfaceDelegate ()
: WaylandServerDelegate::WaylandResource (&::xdg_surface_interface,
static_cast<xdg_surface_interface*> (this))
{
xdg_surface_interface::destroy = destroy;
xdg_surface_interface::get_toplevel = get_toplevel;
xdg_surface_interface::get_popup = get_popup;
xdg_surface_interface::set_window_geometry = set_window_geometry;
xdg_surface_interface::ack_configure = ack_configure;
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::get_toplevel (struct wl_client* client, struct wl_resource* resource,
uint32_t id)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::get_popup (struct wl_client* client, struct wl_resource* resource,
uint32_t id, struct wl_resource* parent,
struct wl_resource* positioner)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::set_window_geometry (struct wl_client* client,
struct wl_resource* resource, int32_t x, int32_t y,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::ack_configure (struct wl_client* client, struct wl_resource* resource,
uint32_t serial)
{
}
//------------------------------------------------------------------------
// XdgToplevelDelegate
//------------------------------------------------------------------------
XdgToplevelDelegate::XdgToplevelDelegate ()
: WaylandServerDelegate::WaylandResource (&::xdg_toplevel_interface,
static_cast<xdg_toplevel_interface*> (this))
{
xdg_toplevel_interface::destroy = destroy;
xdg_toplevel_interface::set_parent = set_parent;
xdg_toplevel_interface::set_title = set_title;
xdg_toplevel_interface::set_app_id = set_app_id;
xdg_toplevel_interface::show_window_menu = show_window_menu;
xdg_toplevel_interface::move = move;
xdg_toplevel_interface::resize = resize;
xdg_toplevel_interface::set_max_size = set_max_size;
xdg_toplevel_interface::set_min_size = set_min_size;
xdg_toplevel_interface::set_maximized = set_maximized;
xdg_toplevel_interface::unset_maximized = unset_maximized;
xdg_toplevel_interface::set_fullscreen = set_fullscreen;
xdg_toplevel_interface::unset_fullscreen = unset_fullscreen;
xdg_toplevel_interface::set_minimized = set_minimized;
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_parent (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* parent)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_title (struct wl_client* client, struct wl_resource* resource,
const char* title)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_app_id (struct wl_client* client, struct wl_resource* resource,
const char* app_id)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::show_window_menu (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, int32_t x,
int32_t y)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::move (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::resize (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, uint32_t edges)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_max_size (struct wl_client* client, struct wl_resource* resource,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_min_size (struct wl_client* client, struct wl_resource* resource,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_maximized (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::unset_maximized (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_fullscreen (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* output)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::unset_fullscreen (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_minimized (struct wl_client* client, struct wl_resource* resource)
{
}
//-----------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/surfacedelegate.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "wayland-server-delegate/iwaylandserver.h"
#include "wayland-server-delegate/waylandresource.h"
#include "xdg-shell-client-protocol.h"
#include "xdg-shell-server-protocol.h"
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// SurfaceDelegate
//------------------------------------------------------------------------
struct SurfaceDelegate : WaylandServerDelegate::WaylandResource, wl_surface_interface
{
SurfaceDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void attach (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* buffer, int32_t x, int32_t y);
static void damage (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height);
static void frame (struct wl_client* client, struct wl_resource* resource, uint32_t callback);
static void set_opaque_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region);
static void set_input_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region);
static void commit (struct wl_client* client, struct wl_resource* resource);
static void set_buffer_transform (struct wl_client* client, struct wl_resource* resource,
int32_t transform);
static void set_buffer_scale (struct wl_client* client, struct wl_resource* resource,
int32_t scale);
static void damage_buffer (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height);
static void offset (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y);
};
//------------------------------------------------------------------------
// XdgSurfaceDelegate
//------------------------------------------------------------------------
struct XdgSurfaceDelegate : WaylandServerDelegate::WaylandResource, xdg_surface_interface
{
XdgSurfaceDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void get_toplevel (struct wl_client* client, struct wl_resource* resource, uint32_t id);
static void get_popup (struct wl_client* client, struct wl_resource* resource, uint32_t id,
struct wl_resource* parent, struct wl_resource* positioner);
static void set_window_geometry (struct wl_client* client, struct wl_resource* resource,
int32_t x, int32_t y, int32_t width, int32_t height);
static void ack_configure (struct wl_client* client, struct wl_resource* resource,
uint32_t serial);
};
//------------------------------------------------------------------------
// XdgToplevelDelegate
//------------------------------------------------------------------------
struct XdgToplevelDelegate : WaylandServerDelegate::WaylandResource, xdg_toplevel_interface
{
XdgToplevelDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void set_parent (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* parent);
static void set_title (struct wl_client* client, struct wl_resource* resource,
const char* title);
static void set_app_id (struct wl_client* client, struct wl_resource* resource,
const char* app_id);
static void show_window_menu (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, int32_t x, int32_t y);
static void move (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial);
static void resize (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, uint32_t edges);
static void set_max_size (struct wl_client* client, struct wl_resource* resource, int32_t width,
int32_t height);
static void set_min_size (struct wl_client* client, struct wl_resource* resource, int32_t width,
int32_t height);
static void set_maximized (struct wl_client* client, struct wl_resource* resource);
static void unset_maximized (struct wl_client* client, struct wl_resource* resource);
static void set_fullscreen (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* output);
static void unset_fullscreen (struct wl_client* client, struct wl_resource* resource);
static void set_minimized (struct wl_client* client, struct wl_resource* resource);
};
//-----------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,594 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/window.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "runloop.h"
#include "surfacedelegate.h"
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
DEF_CLASS_IID (Steinberg::IWaylandFrame)
DEF_CLASS_IID (Steinberg::IWaylandHost);
namespace Steinberg::Vst::EditorHost::Wayland {
namespace {
//------------------------------------------------------------------------
/* Shared memory support code */
void randname (char* buf)
{
struct timespec ts;
clock_gettime (CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i)
{
buf[i] = 'A' + (r & 15) + (r & 16) * 2;
r >>= 5;
}
}
//------------------------------------------------------------------------
int create_shm_file (void)
{
int retries = 100;
do
{
char name[] = "/wl_shm-XXXXXX";
randname (name + sizeof (name) - 7);
--retries;
int fd = shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
{
shm_unlink (name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
//------------------------------------------------------------------------
int allocate_shm_file (size_t size)
{
int fd = create_shm_file ();
if (fd < 0)
return -1;
int ret;
do
{
ret = ftruncate (fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
{
close (fd);
return -1;
}
return fd;
}
//------------------------------------------------------------------------
struct wl_buffer* draw_frame (wl_shm* shm, Size inSize)
{
const int width = inSize.width, height = inSize.height;
int stride = width * 4;
int size = stride * height;
int fd = allocate_shm_file (size);
if (fd == -1)
{
return NULL;
}
auto data = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
close (fd);
return NULL;
}
struct wl_shm_pool* pool = wl_shm_create_pool (shm, fd, size);
struct wl_buffer* buffer =
wl_shm_pool_create_buffer (pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy (pool);
close (fd);
auto int32data = reinterpret_cast<int32_t*> (data);
/* Draw checkerboxed background */
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
if ((x + y / 8 * 8) % 16 < 8)
int32data[y * width + x] = 0xFF666666;
else
int32data[y * width + x] = 0xFFEEEEEE;
}
}
munmap (data, size);
static const struct wl_buffer_listener wlBufferListener = {
.release = [] (void* data, struct wl_buffer* wl_buffer) { wl_buffer_destroy (wl_buffer); },
};
wl_buffer_add_listener (buffer, &wlBufferListener, NULL);
return buffer;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
// WaylandWindow::Impl
//------------------------------------------------------------------------
struct WaylandWindow::Impl
{
using IWaylandClientContext = WaylandServerDelegate::IWaylandClientContext;
Impl (const IWaylandClientContext& waylandClientContext)
: waylandClientContext (waylandClientContext)
{
}
WindowControllerPtr controller;
WindowClosedFunc windowClosedFunc;
IWaylandProxyCreator* wlProxyCreator = nullptr;
const IWaylandClientContext& waylandClientContext;
wl_surface* wlSurface {nullptr};
xdg_surface* xdgSurface {nullptr};
xdg_toplevel* xdgToplevel {nullptr};
zxdg_toplevel_decoration_v1* topLevelDeco {nullptr};
Size size {};
std::string title;
int32_t currentPreferredBufferScale {1};
wl_proxy* wlSurfaceProxy {nullptr};
wl_proxy* xdgSurfaceProxy {nullptr};
wl_proxy* xdgToplevelProxy {nullptr};
IPtr<RunLoop> runLoop;
IPtr<IWaylandHost> host;
using FuncDoClose = std::function<void ()>;
FuncDoClose doCloseFunc;
using FuncOnScaleFactorChanged = std::function<void (float scaleFactor)>;
FuncOnScaleFactorChanged onScaleFactorChangedFunc;
void initSurfaceGeometry (int32_t width, int32_t height);
void addSurfaceListener ();
void addXdgSurfaceListener ();
void addToplevelListener ();
void addTopLevelDecorationListener ();
static void updateScaleFactor (WaylandWindow::Impl* self, int32_t factor);
static void handleXdgSurfaceConfigure (void* data, struct xdg_surface* xdg_surface,
uint32_t serial);
static void handleXdgToplevelConfigure (void* data, struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height, struct wl_array* states);
static void handleXdgToplevelClose (void* data, struct xdg_toplevel* toplevel);
static void handleXdgToplevelConfigureBounds (void* data, struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height);
static void handleXdgToplevelWmCapabilities (void* data, struct xdg_toplevel* xdg_toplevel,
struct wl_array* capabilities);
static void handleTopLevelDecorationConfigure (
void* data, struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode);
static void handleEnter (void* data, wl_surface* wl_surface, wl_output* output);
static void handleLeave (void* data, wl_surface* wl_surface, wl_output* output);
static void handlePreferredBufferScale (void* data, wl_surface* wl_surface, int32_t factor);
static void handlePreferredBufferTransform (void* data, wl_surface* wl_surface,
uint32_t transform);
};
//------------------------------------------------------------------------
WaylandWindow::WaylandWindow (const IWaylandClientContext& waylandClientContext)
{
impl = std::make_unique<Impl> (waylandClientContext);
}
//------------------------------------------------------------------------
WaylandWindow::~WaylandWindow () noexcept
{
}
//------------------------------------------------------------------------
auto WaylandWindow::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller,
const WindowClosedFunc& windowClosedFunc,
IWaylandProxyCreator* wlProxyCreator, IPtr<RunLoop> runLoop,
IPtr<IWaylandHost> host,
const IWaylandClientContext& waylandClientContext) -> Ptr
{
auto window = std::make_shared<WaylandWindow> (waylandClientContext);
if (!window)
return {};
window->impl->runLoop = runLoop;
window->impl->host = host;
window->impl->controller = controller;
window->impl->windowClosedFunc = windowClosedFunc;
window->impl->wlProxyCreator = wlProxyCreator;
window->impl->title = name;
window->impl->size = size;
window->impl->doCloseFunc = [window] () { window->doClose (); };
window->impl->onScaleFactorChangedFunc = [window, controller] (float scaleFactor) {
controller->onContentScaleFactorChanged (*window.get (), scaleFactor);
};
window->impl->wlSurface = wl_compositor_create_surface (waylandClientContext.getCompositor ());
if (!window->impl->wlSurface)
return {};
window->impl->xdgSurface = xdg_wm_base_get_xdg_surface (
waylandClientContext.getWindowManager (), window->impl->wlSurface);
if (!window->impl->xdgSurface)
return {};
window->impl->xdgToplevel = xdg_surface_get_toplevel (window->impl->xdgSurface);
if (!window->impl->xdgToplevel)
return {};
window->impl->addSurfaceListener ();
window->impl->addXdgSurfaceListener ();
window->impl->addToplevelListener ();
window->impl->initSurfaceGeometry (window->impl->size.width, window->impl->size.height);
/* TODO
if (std::string_view (interface) == zxdg_decoration_manager_v1_interface.name)
{
static constexpr uint32_t kVersion = 8;
void* object = wl_registry_bind (
registry, name, &zxdg_decoration_manager_v1_interface, kVersion);
auto deco_manager = reinterpret_cast<zxdg_decoration_manager_v1*> (object);
globals.deco_manager = deco_manager;
return;
}
if (waylandGlobals.deco_manager != nullptr)
{
window->impl->topLevelDeco = zxdg_decoration_manager_v1_get_toplevel_decoration (
waylandGlobals.deco_manager, window->impl->xdgToplevel);
window->impl->addTopLevelDecorationListener ();
zxdg_toplevel_decoration_v1_set_mode (window->impl->topLevelDeco,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
*/
return window;
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgSurfaceConfigure (void* data, struct xdg_surface* xdg_surface,
uint32_t serial)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
if (!self)
return;
xdg_surface_ack_configure (xdg_surface, serial);
auto buffer = draw_frame (self->waylandClientContext.getSharedMemory (), self->size);
wl_surface_set_buffer_scale (self->wlSurface, self->currentPreferredBufferScale);
wl_surface_attach (self->wlSurface, buffer, 0, 0);
wl_surface_commit (self->wlSurface);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelConfigure (void* data, xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height,
wl_array* states)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
if (!self)
return;
if (width > 0 && height > 0)
{
self->size.width = width;
self->size.height = height;
}
xdg_toplevel_set_title (self->xdgToplevel, self->title.data ());
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelClose (void* data, struct xdg_toplevel* toplevel)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
if (self && self->doCloseFunc)
self->doCloseFunc ();
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelConfigureBounds (void* data,
struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelWmCapabilities (void* data,
struct xdg_toplevel* xdg_toplevel,
struct wl_array* capabilities)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleTopLevelDecorationConfigure (
void* data, struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::updateScaleFactor (WaylandWindow::Impl* self, int32_t factor)
{
self->currentPreferredBufferScale = factor;
wl_surface_set_buffer_scale (self->wlSurface, factor);
wl_surface_commit (self->wlSurface);
if (self->onScaleFactorChangedFunc)
self->onScaleFactorChangedFunc (factor);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleEnter (void* data, wl_surface* wl_surface, wl_output* output)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
if (!self)
return;
if (wl_surface != self->wlSurface)
return;
#if WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION
// 'preferred_buffer_scale' is handling the scale factor
#else
const auto count = self->waylandClientContext.countOutputs ();
for (auto i = 0; i < count; ++i)
{
const auto& wlOutput = self->waylandClientContext.getOutput (i);
if (wlOutput.handle != output)
continue;
updateScaleFactor (self, wlOutput.scaleFactor);
}
#endif
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleLeave (void* data, wl_surface* wl_surface, wl_output* output)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handlePreferredBufferScale (void* data, wl_surface* wl_surface,
int32_t factor)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
if (!self)
return;
if (wl_surface != self->wlSurface)
return;
updateScaleFactor (self, factor);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handlePreferredBufferTransform (void* data, wl_surface* wl_surface,
uint32_t transform)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::doClose ()
{
impl->controller->onClose (*this);
if (impl->windowClosedFunc)
impl->windowClosedFunc (this);
if (impl->wlProxyCreator)
{
if (impl->wlSurfaceProxy)
impl->wlProxyCreator->destroyProxy (impl->wlSurfaceProxy);
if (impl->xdgSurfaceProxy)
impl->wlProxyCreator->destroyProxy (impl->xdgSurfaceProxy);
if (impl->xdgToplevelProxy)
impl->wlProxyCreator->destroyProxy (impl->xdgToplevelProxy);
}
impl->xdgToplevelProxy = {};
impl->xdgSurfaceProxy = {};
impl->wlSurfaceProxy = {};
if (impl->xdgToplevel)
xdg_toplevel_destroy (impl->xdgToplevel);
if (impl->xdgSurface)
xdg_surface_destroy (impl->xdgSurface);
if (impl->wlSurface)
wl_surface_destroy (impl->wlSurface);
impl->xdgToplevel = {};
impl->xdgSurface = {};
impl->wlSurface = {};
}
//------------------------------------------------------------------------
void WaylandWindow::show ()
{
impl->controller->onShow (*this);
}
//------------------------------------------------------------------------
void WaylandWindow::close ()
{
impl->controller->onClose (*this);
}
//------------------------------------------------------------------------
void WaylandWindow::resize (Size newSize)
{
impl->size = newSize;
if (impl->xdgSurface)
xdg_surface_set_window_geometry (impl->xdgSurface, 0, 0, impl->size.width,
impl->size.height);
impl->controller->onResize (*this, impl->size);
}
//------------------------------------------------------------------------
Size WaylandWindow::getContentSize ()
{
return impl->size;
}
//------------------------------------------------------------------------
NativePlatformWindow WaylandWindow::getNativePlatformWindow () const
{
return {kPlatformTypeWaylandSurfaceID, nullptr};
}
//------------------------------------------------------------------------
wl_surface* PLUGIN_API WaylandWindow::getWaylandSurface (wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->wlSurfaceProxy)
return reinterpret_cast<wl_surface*> (impl->wlSurfaceProxy);
impl->wlSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->wlSurface), new SurfaceDelegate);
return reinterpret_cast<wl_surface*> (impl->wlSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_surface* PLUGIN_API WaylandWindow::getParentSurface (ViewRect& parentSize, wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgSurfaceProxy)
return reinterpret_cast<xdg_surface*> (impl->xdgSurfaceProxy);
impl->xdgSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->xdgSurface), new XdgSurfaceDelegate);
parentSize = ViewRect (0, 0, impl->size.width, impl->size.height);
return reinterpret_cast<xdg_surface*> (impl->xdgSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_toplevel* PLUGIN_API WaylandWindow::getParentToplevel (wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgToplevelProxy)
return reinterpret_cast<xdg_toplevel*> (impl->xdgToplevelProxy);
impl->xdgToplevelProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->xdgToplevelProxy), new XdgToplevelDelegate);
return reinterpret_cast<xdg_toplevel*> (impl->xdgToplevelProxy);
}
//------------------------------------------------------------------------
tresult WaylandWindow::queryInterface (const TUID iid, void** obj)
{
if (FUnknownPrivate::iidEqual (iid, IWaylandFrame::iid))
{
*obj = static_cast<IWaylandFrame*> (this);
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, IWaylandHost::iid))
{
*obj = static_cast<IWaylandHost*> (impl->host);
impl->host->addRef ();
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
{
*obj = static_cast<Linux::IRunLoop*> (impl->runLoop);
impl->runLoop->addRef ();
return kResultTrue;
}
return kNoInterface;
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::initSurfaceGeometry (int32_t width, int32_t height)
{
xdg_surface_set_window_geometry (xdgSurface, 0, 0, size.width, size.height);
wl_surface_commit (wlSurface);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addSurfaceListener ()
{
static const wl_surface_listener listener = {
.enter = handleEnter,
.leave = handleLeave,
.preferred_buffer_scale = handlePreferredBufferScale,
.preferred_buffer_transform = handlePreferredBufferTransform,
};
wl_surface_add_listener (wlSurface, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addXdgSurfaceListener ()
{
static const xdg_surface_listener listener = {
.configure = handleXdgSurfaceConfigure,
};
xdg_surface_add_listener (xdgSurface, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addToplevelListener ()
{
static const struct xdg_toplevel_listener listener = {
.configure = handleXdgToplevelConfigure,
.close = handleXdgToplevelClose,
.configure_bounds = handleXdgToplevelConfigureBounds,
.wm_capabilities = handleXdgToplevelWmCapabilities,
};
xdg_toplevel_add_listener (xdgToplevel, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addTopLevelDecorationListener ()
{
static struct zxdg_toplevel_decoration_v1_listener zxdgTopLevelDecorationListener = {
.configure = handleTopLevelDecorationConfigure,
};
zxdg_toplevel_decoration_v1_add_listener (topLevelDeco, &zxdgTopLevelDecorationListener, this);
}
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/window.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include "runloop.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iwaylandframe.h"
#include <functional>
#include <unordered_map>
namespace WaylandServerDelegate {
class IWaylandClientContext;
class WaylandResource;
}
struct wl_compositor;
struct xdg_wm_base;
struct wl_shm;
struct zxdg_decoration_manager_v1;
struct wl_proxy;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// IWaylandProxyCreator, see also: IWaylandServer
//------------------------------------------------------------------------
struct IWaylandProxyCreator
{
virtual wl_proxy* createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation) = 0;
virtual void destroyProxy (wl_proxy* proxy) = 0;
virtual ~IWaylandProxyCreator () noexcept = default;
};
//------------------------------------------------------------------------
// WaylandWindow
//------------------------------------------------------------------------
class WaylandWindow : public IWindow,
private U::ImplementsNonDestroyable<U::Directly<IWaylandFrame>>
{
public:
//------------------------------------------------------------------------
using Ptr = std::shared_ptr<WaylandWindow>;
using WindowClosedFunc = std::function<void (WaylandWindow*)>;
using IWaylandClientContext = WaylandServerDelegate::IWaylandClientContext;
static Ptr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller,
const WindowClosedFunc& windowClosedFunc, IWaylandProxyCreator* proxyCreator,
IPtr<RunLoop> runLoop, IPtr<IWaylandHost> host,
const IWaylandClientContext& waylandContext);
WaylandWindow (const IWaylandClientContext& waylandContext);
~WaylandWindow () noexcept override;
//------------------------------------------------------------------------
private:
void doClose ();
// IWindow
void show () override;
void close () override;
void resize (Size newSize) override;
Size getContentSize () override;
NativePlatformWindow getNativePlatformWindow () const override;
tresult queryInterface (const TUID iid, void** obj) override;
// IWaylandFrame
wl_surface* PLUGIN_API getWaylandSurface (wl_display* display) override;
xdg_surface* PLUGIN_API getParentSurface (ViewRect& parentSize, wl_display* display) override;
xdg_toplevel* PLUGIN_API getParentToplevel (wl_display* display) override;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,626 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.cpp
// Created by : Steinberg 05.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include <cassert>
#include <iostream>
#include <unordered_map>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct X11Window::Impl
{
Impl (X11Window* x11Window);
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
void show ();
void close ();
Size getSize () const;
void resize (Size newSize, bool force);
//------------------------------------------------------------------------
struct XEmbedInfo
{
uint32_t version;
uint32_t flags;
};
~Impl ();
void onClose ();
bool handleMainWindowEvent (const XEvent& event);
bool handlePlugEvent (const XEvent& event);
XEmbedInfo* getXEmbedInfo ();
void checkSize ();
void callPlugEventHandlers ();
WindowControllerPtr controller {nullptr};
WindowClosedFunc windowClosedFunc;
Display* xDisplay {nullptr};
XEmbedInfo* xembedInfo {nullptr};
Window xWindow {};
Window plugParentWindow {};
Window plugWindow {};
GC xGraphicContext {};
Atom xEmbedInfoAtom {None};
Atom xEmbedAtom {None};
bool isMapped {false};
using EventHandler = IPtr<Linux::IEventHandler>;
using TimerHandler = IPtr<Linux::ITimerHandler>;
Size mCurrentSize {};
X11Window* x11Window {nullptr};
};
//------------------------------------------------------------------------
auto X11Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc) -> Ptr
{
auto window = std::make_shared<X11Window> ();
if (window->init (name, size, resizeable, controller, display, windowClosedFunc))
{
return window;
}
return nullptr;
}
//------------------------------------------------------------------------
X11Window::X11Window ()
{
impl = std::unique_ptr<Impl> (new Impl (this));
}
//------------------------------------------------------------------------
X11Window::~X11Window ()
{
}
//------------------------------------------------------------------------
bool X11Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc)
{
return impl->init (name, size, resizeable, controller, display, windowClosedFunc);
}
//------------------------------------------------------------------------
Size X11Window::getSize () const
{
return impl->getSize ();
}
//------------------------------------------------------------------------
void X11Window::show ()
{
impl->show ();
}
//------------------------------------------------------------------------
void X11Window::close ()
{
impl->close ();
}
//------------------------------------------------------------------------
void X11Window::resize (Size newSize)
{
impl->resize (newSize, false);
}
//------------------------------------------------------------------------
Size X11Window::getContentSize ()
{
return {};
}
//------------------------------------------------------------------------
NativePlatformWindow X11Window::getNativePlatformWindow () const
{
return {kPlatformTypeX11EmbedWindowID, reinterpret_cast<void*> (impl->plugParentWindow)};
}
//------------------------------------------------------------------------
tresult X11Window::queryInterface (const TUID iid, void** obj)
{
if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
{
*obj = &(Linux::RunLoopImpl::instance ());
// as the run loop is a singleton it is not necessary to call a retain on that object
return kResultTrue;
}
return kNoInterface;
}
//------------------------------------------------------------------------
void X11Window::onIdle ()
{
}
/* XEMBED messages */
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_WINDOW_ACTIVATE 1
#define XEMBED_WINDOW_DEACTIVATE 2
#define XEMBED_REQUEST_FOCUS 3
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
#define XEMBED_FOCUS_NEXT 6
#define XEMBED_FOCUS_PREV 7
/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
#define XEMBED_MODALITY_ON 10
#define XEMBED_MODALITY_OFF 11
#define XEMBED_REGISTER_ACCELERATOR 12
#define XEMBED_UNREGISTER_ACCELERATOR 13
#define XEMBED_ACTIVATE_ACCELERATOR 14
void send_xembed_message (Display* dpy, /* display */
Window w, /* receiver */
Atom messageType, long message, /* message opcode */
long detail, /* message detail */
long data1, /* message data 1 */
long data2 /* message data 2 */
)
{
XEvent ev;
memset (&ev, 0, sizeof (ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = w;
ev.xclient.message_type = messageType;
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = message;
ev.xclient.data.l[2] = detail;
ev.xclient.data.l[3] = data1;
ev.xclient.data.l[4] = data2;
XSendEvent (dpy, w, False, NoEventMask, &ev);
XSync (dpy, False);
}
#define XEMBED_MAPPED (1 << 0)
//------------------------------------------------------------------------
X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window)
{
}
//------------------------------------------------------------------------
X11Window::Impl::~Impl ()
{
}
//------------------------------------------------------------------------
bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc)
{
this->windowClosedFunc = windowClosedFunc;
this->controller = controller;
xDisplay = display;
xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
if (xEmbedInfoAtom == None)
{
std::cerr << "_XEMBED_INFO does not exist" << std::endl;
return false;
}
// Get screen size from display
auto screen_num = DefaultScreen (xDisplay);
auto displayWidth = DisplayWidth (xDisplay, screen_num);
auto displayHeight = DisplayHeight (xDisplay, screen_num);
unsigned int border_width = 1;
XVisualInfo vInfo;
if (!XMatchVisualInfo (xDisplay, screen_num, 24, TrueColor, &vInfo))
{
exit (-1);
}
XSetWindowAttributes winAttr {};
winAttr.border_pixel = BlackPixel (xDisplay, screen_num);
winAttr.background_pixel = WhitePixel (xDisplay, screen_num);
winAttr.colormap =
XCreateColormap (xDisplay, XDefaultRootWindow (xDisplay), vInfo.visual, AllocNone);
uint32_t winAttrMask = CWBackPixel | CWColormap | CWBorderPixel;
xWindow = XCreateWindow (xDisplay, RootWindow (xDisplay, screen_num), 0, 0, displayWidth,
displayHeight, border_width, vInfo.depth, InputOutput, vInfo.visual,
winAttrMask, &winAttr);
XFlush (xDisplay);
resize (size, true);
XSelectInput (xDisplay, xWindow, /* KeyPressMask | ButtonPressMask |*/
ExposureMask | /*ResizeRedirectMask |*/ StructureNotifyMask |
SubstructureNotifyMask | FocusChangeMask);
auto sizeHints = XAllocSizeHints ();
sizeHints->flags = PMinSize;
if (!resizeable)
{
sizeHints->flags |= PMaxSize;
sizeHints->min_width = sizeHints->max_width = size.width;
sizeHints->min_height = sizeHints->max_height = size.height;
}
else
{
sizeHints->min_width = sizeHints->min_height = 80;
}
XSetWMNormalHints (xDisplay, xWindow, sizeHints);
XFree (sizeHints);
// set a title
XStoreName (xDisplay, xWindow, name.data ());
XTextProperty iconName;
auto icon_name = const_cast<char*> (name.data ());
XStringListToTextProperty (&icon_name, 1, &iconName);
XSetWMIconName (xDisplay, xWindow, &iconName);
Atom wm_delete_window;
wm_delete_window = XInternAtom (xDisplay, "WM_DELETE_WINDOW", False);
XSetWMProtocols (xDisplay, xWindow, &wm_delete_window, 1);
xGraphicContext = XCreateGC (xDisplay, xWindow, 0, 0);
XSetForeground (xDisplay, xGraphicContext, WhitePixel (xDisplay, screen_num));
XSetBackground (xDisplay, xGraphicContext, BlackPixel (xDisplay, screen_num));
winAttr = {};
winAttr.override_redirect = true;
winAttr.event_mask =
ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask;
plugParentWindow =
XCreateWindow (xDisplay, xWindow, 0, 0, size.width, size.height, border_width, vInfo.depth,
InputOutput, CopyFromParent, winAttrMask, &winAttr);
XSelectInput (xDisplay, plugParentWindow, SubstructureNotifyMask | PropertyChangeMask);
XMapWindow (xDisplay, plugParentWindow);
RunLoop::instance ().registerWindow (plugParentWindow,
[this] (const XEvent& e) { return handlePlugEvent (e); });
RunLoop::instance ().registerWindow (
xWindow, [this] (const XEvent& e) { return handleMainWindowEvent (e); });
return true;
}
//------------------------------------------------------------------------
void X11Window::Impl::show ()
{
XMapWindow (xDisplay, xWindow);
}
//------------------------------------------------------------------------
void X11Window::Impl::close ()
{
XUnmapWindow (xDisplay, xWindow);
}
//------------------------------------------------------------------------
void X11Window::Impl::onClose ()
{
XFreeGC (xDisplay, xGraphicContext);
XDestroyWindow (xDisplay, xWindow);
xDisplay = nullptr;
xWindow = 0;
isMapped = false;
if (windowClosedFunc)
windowClosedFunc (x11Window);
}
//------------------------------------------------------------------------
void X11Window::Impl::resize (Size newSize, bool force)
{
if (!force && mCurrentSize == newSize)
return;
if (xWindow)
XResizeWindow (xDisplay, xWindow, newSize.width, newSize.height);
if (plugParentWindow)
XResizeWindow (xDisplay, plugParentWindow, newSize.width, newSize.height);
mCurrentSize = newSize;
}
//------------------------------------------------------------------------
Size X11Window::Impl::getSize () const
{
::Window root;
int x, y;
unsigned int width, height;
unsigned int border_width;
unsigned int depth;
XGetGeometry (xDisplay, xWindow, &root, &x, &y, &width, &height, &border_width, &depth);
return {static_cast<int> (width), static_cast<int> (height)};
}
//------------------------------------------------------------------------
void X11Window::Impl::checkSize ()
{
if (getSize () != mCurrentSize)
{
resize (mCurrentSize, true);
}
}
//------------------------------------------------------------------------
bool X11Window::Impl::handleMainWindowEvent (const XEvent& event)
{
#if LOG_EVENTS
std::cout << "event " << event.type << "\n";
#endif
bool res = false;
switch (event.type)
{
case Expose:
if (event.xexpose.count == 0)
{
XClearWindow (xDisplay, xWindow);
XFillRectangle (xDisplay, xWindow, xGraphicContext, 0, 0, mCurrentSize.width,
mCurrentSize.height);
}
res = true;
break;
//--- StructureNotifyMask ------------------------------
// Window has been resized
case ConfigureNotify:
{
if (event.xconfigure.window != xWindow)
break;
auto width = event.xconfigure.width;
auto height = event.xconfigure.height;
Size size {width, height};
if (mCurrentSize != size)
{
auto constraintSize = controller->constrainSize (*x11Window, size);
if (constraintSize != mCurrentSize)
{
mCurrentSize = size;
controller->onResize (*x11Window, size);
}
if (constraintSize != size)
resize (constraintSize, true);
else
{
if (plugParentWindow)
XResizeWindow (xDisplay, plugParentWindow, size.width, size.height);
}
#if LOG_EVENTS
std::cout << "new size " << width << " x " << height << "\n";
#endif
}
res = true;
}
break;
// Window has been map to the screen
case MapNotify:
{
if (event.xany.window == xWindow && !isMapped)
{
controller->onShow (*x11Window);
isMapped = true;
res = true;
}
}
break;
case UnmapNotify:
{
if (event.xunmap.window == xWindow)
{
controller->onClose (*x11Window);
onClose ();
res = true;
}
break;
}
case DestroyNotify: break;
case ClientMessage:
{
if (event.xany.window == xWindow)
{
controller->onClose (*x11Window);
onClose ();
res = true;
}
break;
}
case FocusIn:
{
if (xembedInfo)
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
plugParentWindow, xembedInfo->version);
break;
}
case FocusOut:
{
if (xembedInfo)
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_DEACTIVATE, 0,
plugParentWindow, xembedInfo->version);
break;
}
//--- ResizeRedirectMask --------------------------------
case ResizeRequest:
{
if (event.xany.window == xWindow)
{
auto width = event.xresizerequest.width;
auto height = event.xresizerequest.height;
Size request {width, height};
if (mCurrentSize != request)
{
#if LOG_EVENTS
std::cout << "requested Size " << width << " x " << height << "\n";
#endif
auto constraintSize = controller->constrainSize (*x11Window, request);
if (constraintSize != request)
{
#if LOG_EVENTS
std::cout << "constraint Size " << constraintSize.width << " x "
<< constraintSize.height << "\n";
#endif
}
resize (constraintSize, true);
}
res = true;
}
}
break;
}
return res;
}
//------------------------------------------------------------------------
auto X11Window::Impl::getXEmbedInfo () -> XEmbedInfo*
{
int actualFormat;
unsigned long itemsReturned;
unsigned long bytesAfterReturn;
Atom actualType;
XEmbedInfo* xembedInfo = NULL;
if (xEmbedInfoAtom == None)
xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
auto err =
XGetWindowProperty (xDisplay, plugWindow, xEmbedInfoAtom, 0, sizeof (xembedInfo), false,
xEmbedInfoAtom, &actualType, &actualFormat, &itemsReturned,
&bytesAfterReturn, reinterpret_cast<unsigned char**> (&xembedInfo));
if (err != Success)
return nullptr;
return xembedInfo;
}
//------------------------------------------------------------------------
bool X11Window::Impl::handlePlugEvent (const XEvent& event)
{
bool res = false;
switch (event.type)
{
// XEMBED specific
case ClientMessage:
{
auto name = XGetAtomName (xDisplay, event.xclient.message_type);
std::cout << name << std::endl;
if (event.xclient.message_type == xEmbedAtom)
{
switch (event.xclient.data.l[1])
{
case XEMBED_REQUEST_FOCUS:
{
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
plugParentWindow, xembedInfo->version);
break;
}
}
}
break;
}
case PropertyNotify:
{
auto name = XGetAtomName (xDisplay, event.xproperty.atom);
std::cout << name << std::endl;
if (event.xany.window == plugWindow)
{
if (event.xproperty.atom == xEmbedInfoAtom)
{
if (auto embedInfo = getXEmbedInfo ())
{
}
}
else
{
}
}
break;
}
case CreateNotify:
{
if (event.xcreatewindow.parent != plugParentWindow)
{
res = true;
break;
}
plugWindow = event.xcreatewindow.window;
xembedInfo = getXEmbedInfo ();
if (!xembedInfo)
{
std::cerr << "XGetWindowProperty for _XEMBED_INFO failed" << std::endl;
exit (-1);
}
if (xembedInfo->flags & XEMBED_MAPPED)
{
std::cerr << "Window already mapped error" << std::endl;
exit (-1);
}
RunLoop::instance ().registerWindow (
plugWindow, [this] (const XEvent& e) { return handlePlugEvent (e); });
// XSelectInput (xDisplay, plugWindow, PropertyChangeMask);
if (xEmbedAtom == None)
xEmbedAtom = XInternAtom (xDisplay, "_XEMBED", true);
assert (xEmbedAtom != None);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_EMBEDDED_NOTIFY, 0,
plugParentWindow, xembedInfo->version);
XMapWindow (xDisplay, plugWindow);
XResizeWindow (xDisplay, plugWindow, mCurrentSize.width, mCurrentSize.height);
// XSetInputFocus (xDisplay, plugWindow, RevertToParent, CurrentTime);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
plugParentWindow, xembedInfo->version);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
plugParentWindow, xembedInfo->version);
XSync (xDisplay, False);
res = true;
break;
}
}
return res;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include <functional>
struct _XDisplay;
typedef struct _XDisplay Display;
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class X11Window : public IWindow
{
public:
using Ptr = std::shared_ptr<X11Window>;
using WindowClosedFunc = std::function<void (X11Window*)>;
static Ptr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
X11Window ();
~X11Window () override;
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
void show () override;
void close () override;
void resize (Size newSize) override;
Size getContentSize () override;
NativePlatformWindow getNativePlatformWindow () const override;
tresult queryInterface (const TUID iid, void** obj) override;
void onIdle ();
private:
Size getSize () const;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,222 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/platform.mm
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h"
#import <Cocoa/Cocoa.h>
#import <iostream>
#if !__has_feature(objc_arc)
#error this file needs to be compiled with automatic reference counting enabled
#endif
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Platform : public IPlatform
{
public:
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
Platform ();
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
ApplicationPtr application;
bool quitRequested {false};
};
//------------------------------------------------------------------------
Platform::Platform ()
{
NSApplicationLoad ();
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
return Window::make (title, size, resizeable, controller);
}
//------------------------------------------------------------------------
void Platform::quit ()
{
if (quitRequested)
return;
quitRequested = true;
dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t) (0.1 * NSEC_PER_SEC)),
dispatch_get_main_queue (), ^{
@autoreleasepool
{
for (NSWindow* window in NSApp.windows)
[window close];
}
application = nullptr;
[NSApp terminate:nil];
});
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
std::cout.flush ();
auto alert = [NSAlert new];
alert.messageText = [NSString stringWithUTF8String:reason.data ()];
alert.alertStyle = NSCriticalAlertStyle;
[alert addButtonWithTitle:@"Quit"];
[alert runModal];
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return nullptr;
}
//------------------------------------------------------------------------
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
@interface VSTSDK_AppDelegate : NSObject <NSApplicationDelegate>
{
std::vector<std::string> cmdArgs;
}
@end
//------------------------------------------------------------------------
@implementation VSTSDK_AppDelegate
//------------------------------------------------------------------------
- (void)setCmdArgs:(std::vector<std::string>&&)args
{
cmdArgs = std::move (args);
}
//------------------------------------------------------------------------
- (NSMenu*)createAppMenu
{
auto appName =
static_cast<NSString*> ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]);
NSMenu* menu = [[NSMenu alloc] initWithTitle:appName];
[menu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
action:@selector (hide:)
keyEquivalent:@"h"];
[menu addItemWithTitle:@"Hide Others"
action:@selector (hideOtherApplications:)
keyEquivalent:@""];
[menu addItemWithTitle:@"Show All" action:@selector (unhideAllApplications:) keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
action:@selector (terminate:)
keyEquivalent:@"q"];
return menu;
}
//------------------------------------------------------------------------
- (NSMenu*)createFileMenu
{
NSMenu* menu = [[NSMenu alloc] initWithTitle:@"File"];
[menu addItemWithTitle:@"Close Window" action:@selector (performClose:) keyEquivalent:@"w"];
return menu;
}
//------------------------------------------------------------------------
- (void)setupMenubar
{
auto mainMenu = [NSMenu new];
[NSApp setMainMenu:mainMenu];
auto appMenuItem = [[NSMenuItem alloc] initWithTitle:@"App" action:nil keyEquivalent:@""];
[mainMenu addItem:appMenuItem];
appMenuItem.submenu = [self createAppMenu];
auto fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
[mainMenu addItem:fileMenuItem];
fileMenuItem.submenu = [self createFileMenu];
}
//------------------------------------------------------------------------
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename
{
if (cmdArgs.empty ())
cmdArgs.push_back ([filename UTF8String]);
return YES;
}
//------------------------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
[self setupMenubar];
Steinberg::Vst::EditorHost::Platform::instance ().application->init (cmdArgs);
cmdArgs.clear ();
}
//------------------------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification*)notification
{
if (auto& app = Steinberg::Vst::EditorHost::Platform::instance ().application)
app->terminate ();
}
@end
//------------------------------------------------------------------------
int main (int argc, const char* argv[])
{
auto delegate = [VSTSDK_AppDelegate new];
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
[delegate setCmdArgs:std::move (cmdArgs)];
[NSApplication sharedApplication].delegate = delegate;
return NSApplicationMain (argc, argv);
}
@@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Window : public IWindow, public std::enable_shared_from_this<Window>
{
public:
static WindowPtr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller);
Window ();
~Window () noexcept override;
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller);
void show () override;
void close () override;
void resize (Size newSize) override;
Size getContentSize () override;
NativePlatformWindow getNativePlatformWindow () const override;
tresult queryInterface (const TUID iid, void** obj) override { return kNoInterface; }
WindowControllerPtr getController () const;
void windowClosed ();
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,209 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.mm
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h"
#import <Cocoa/Cocoa.h>
#if !__has_feature(objc_arc)
#error this file needs to be compiled with automatic reference counting enabled
#endif
using namespace Steinberg::Vst;
//------------------------------------------------------------------------
@interface VSTSDK_WindowDelegate : NSObject <NSWindowDelegate>
#if __i386__
{
std::shared_ptr<EditorHost::Window> _window;
NSWindow* _nsWindow;
}
#endif
@property (readonly) std::shared_ptr<EditorHost::Window> window;
@property (strong, readwrite) NSWindow* nsWindow;
- (id)initWithWindow:(std::shared_ptr<EditorHost::Window>)window;
@end
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct Window::Impl
{
WindowControllerPtr controller;
VSTSDK_WindowDelegate* nsWindowDelegate {nullptr};
};
//------------------------------------------------------------------------
WindowPtr Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = std::make_shared<Window> ();
if (window->init (name, size, resizeable, controller))
return window;
return nullptr;
}
//------------------------------------------------------------------------
Window::Window ()
{
impl = std::unique_ptr<Impl> (new Impl);
}
//------------------------------------------------------------------------
bool Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
impl->controller = controller;
NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (resizeable)
styleMask |= NSWindowStyleMaskResizable;
auto contentRect =
NSMakeRect (0., 0., static_cast<CGFloat> (size.width), static_cast<CGFloat> (size.height));
impl->nsWindowDelegate = [[VSTSDK_WindowDelegate alloc] initWithWindow:shared_from_this ()];
auto nsWindow = [[NSWindow alloc] initWithContentRect:contentRect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES];
[nsWindow setDelegate:impl->nsWindowDelegate];
nsWindow.releasedWhenClosed = NO;
impl->nsWindowDelegate.nsWindow = nsWindow;
[nsWindow center];
return true;
}
//------------------------------------------------------------------------
Window::~Window () noexcept
{
}
//------------------------------------------------------------------------
void Window::show ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
impl->controller->onShow (*this);
[nsWindow makeKeyAndOrderFront:nil];
}
//------------------------------------------------------------------------
void Window::close ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
[nsWindow close];
}
//------------------------------------------------------------------------
void Window::resize (Size newSize)
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
auto r = [nsWindow contentRectForFrameRect:nsWindow.frame];
auto diff = newSize.height - r.size.height;
r.size.width = newSize.width;
r.size.height = newSize.height;
r.origin.y -= diff;
[nsWindow setFrame:[nsWindow frameRectForContentRect:r]
display:[nsWindow isVisible]
animate:NO];
}
//------------------------------------------------------------------------
Size Window::getContentSize ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
auto r = [nsWindow contentRectForFrameRect:nsWindow.frame];
return {static_cast<Coord> (r.size.width), static_cast<Coord> (r.size.height)};
}
//------------------------------------------------------------------------
NativePlatformWindow Window::getNativePlatformWindow () const
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
return {kPlatformTypeNSView, (__bridge void*)([nsWindow contentView])};
}
//------------------------------------------------------------------------
WindowControllerPtr Window::getController () const
{
return impl->controller;
}
//------------------------------------------------------------------------
void Window::windowClosed ()
{
impl->controller->onClose (*this);
impl->nsWindowDelegate = nullptr;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
@implementation VSTSDK_WindowDelegate
#if __i386__
@synthesize window = _window;
#endif
//------------------------------------------------------------------------
- (id)initWithWindow:(std::shared_ptr<EditorHost::Window>)window
{
if (self = [super init])
{
self->_window = window;
}
return self;
}
//------------------------------------------------------------------------
- (NSSize)windowWillResize:(nonnull NSWindow*)sender toSize:(NSSize)frameSize
{
NSRect r {};
r.size = frameSize;
r = [sender contentRectForFrameRect:r];
EditorHost::Size size {static_cast<EditorHost::Coord> (r.size.width),
static_cast<EditorHost::Coord> (r.size.height)};
size = self.window->getController ()->constrainSize (*self.window, size);
r.size.width = size.width;
r.size.height = size.height;
r = [sender frameRectForContentRect:r];
return r.size;
}
//------------------------------------------------------------------------
- (void)windowDidResize:(nonnull NSNotification*)notification
{
NSWindow* window = [notification object];
NSRect r = window.frame;
r = [window contentRectForFrameRect:r];
EditorHost::Size size {static_cast<EditorHost::Coord> (r.size.width),
static_cast<EditorHost::Coord> (r.size.height)};
self.window->getController ()->onResize (*self.window, size);
}
//------------------------------------------------------------------------
- (void)windowWillClose:(nonnull NSNotification*)notification
{
self.window->windowClosed ();
self->_window = nullptr;
self->_nsWindow = nullptr;
}
@end
@@ -0,0 +1,163 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/platform.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/base/ftypes.h"
#include <algorithm>
#include <objbase.h>
#include <shellapi.h>
#include <string>
#include <vector>
#include <windows.h>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Platform : public IPlatform
{
public:
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
void run (LPWSTR lpCmdLine, HINSTANCE instance);
private:
ApplicationPtr application;
HINSTANCE hInstance {nullptr};
bool quitRequested {false};
};
//------------------------------------------------------------------------
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
return Window::make (title, size, resizeable, controller, hInstance);
}
//------------------------------------------------------------------------
void Platform::quit ()
{
if (quitRequested)
return;
quitRequested = true;
for (auto& window : Window::getWindows ())
window->closeImmediately ();
if (application)
application->terminate ();
PostQuitMessage (0);
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
auto str = StringConvert::convert (reason);
MessageBox (nullptr, reinterpret_cast<LPCWSTR> (str.data ()), nullptr, MB_OK);
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return nullptr;
}
//------------------------------------------------------------------------
void Platform::run (LPWSTR lpCmdLine, HINSTANCE _hInstance)
{
hInstance = _hInstance;
std::vector<std::string> cmdArgStrings;
int numArgs = 0;
auto cmdArgsArray = CommandLineToArgvW (lpCmdLine, &numArgs);
cmdArgStrings.reserve (numArgs);
for (int i = 0; i < numArgs; ++i)
{
cmdArgStrings.push_back (StringConvert::convert (Steinberg::wscast (cmdArgsArray[i])));
}
LocalFree (cmdArgsArray);
auto noHIDPI = std::find (cmdArgStrings.begin (), cmdArgStrings.end (), "-noHIDPI");
if (noHIDPI == cmdArgStrings.end ())
ShcoreLibrary::instance ().setProcessDpiAwareness (true);
application->init (cmdArgStrings);
MSG msg;
while (GetMessage (&msg, nullptr, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
#ifndef _In_
#define _In_
#endif
#ifndef _In_opt_
#define _In_opt_
#endif
//------------------------------------------------------------------------
int APIENTRY wWinMain (_In_ HINSTANCE instance, _In_opt_ HINSTANCE /*prevInstance*/,
_In_ LPWSTR lpCmdLine, _In_ int /*nCmdShow*/)
{
HRESULT hr = CoInitialize (nullptr);
if (FAILED (hr))
return FALSE;
Steinberg::Vst::EditorHost::Platform::instance ().run (lpCmdLine, instance);
CoUninitialize ();
return 0;
}
@@ -0,0 +1,298 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include <algorithm>
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
#ifndef WM_GETDPISCALEDSIZE
#define WM_GETDPISCALEDSIZE 0x02E4
#endif
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
namespace {
static Window::WindowList gAllWindows;
//------------------------------------------------------------------------
static void addWindow (Window* window)
{
gAllWindows.push_back (window);
}
//------------------------------------------------------------------------
static void removeWindow (Window* window)
{
auto it = std::find (gAllWindows.begin (), gAllWindows.end (), window);
if (it != gAllWindows.end ())
gAllWindows.erase (it);
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
static const WCHAR* gWindowClassName = L"VSTSDK WindowClass";
//------------------------------------------------------------------------
WindowPtr Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance)
{
auto window = std::make_shared<Window> ();
if (window->init (name, size, resizeable, controller, instance))
return window;
return nullptr;
}
//------------------------------------------------------------------------
LRESULT CALLBACK Window::WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
auto* window = reinterpret_cast<Window*> ((LONG_PTR)GetWindowLongPtr (hWnd, GWLP_USERDATA));
if (window)
return window->proc (message, wParam, lParam);
return DefWindowProc (hWnd, message, wParam, lParam);
}
//------------------------------------------------------------------------
void Window::registerWindowClass (HINSTANCE instance)
{
static bool once = true;
if (!once)
return;
once = true;
WNDCLASSEX wcex {};
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hCursor = LoadCursor (instance, IDC_ARROW);
wcex.hbrBackground = nullptr;
wcex.lpszClassName = gWindowClassName;
RegisterClassEx (&wcex);
}
//------------------------------------------------------------------------
auto Window::getWindows () -> WindowList
{
return gAllWindows;
}
//------------------------------------------------------------------------
bool Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& _controller, HINSTANCE instance)
{
controller = _controller;
registerWindowClass (instance);
DWORD exStyle = WS_EX_APPWINDOW;
DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
if (resizeable)
dwStyle |= WS_SIZEBOX | WS_MAXIMIZEBOX;
auto windowTitle = Vst::StringConvert::convert (name);
RECT rect {0, 0, size.width, size.height};
AdjustWindowRectEx (&rect, dwStyle, false, exStyle);
hwnd = CreateWindowEx (exStyle, gWindowClassName, (const TCHAR*)windowTitle.data (), dwStyle, 0,
0, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr,
instance, nullptr);
if (hwnd)
{
SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR)this);
This = shared_from_this ();
addWindow (this);
}
return hwnd != nullptr;
}
//------------------------------------------------------------------------
LRESULT Window::proc (UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
{
return TRUE; // do not draw background
}
case WM_PAINT:
{
PAINTSTRUCT ps {};
BeginPaint (hwnd, &ps);
EndPaint (hwnd, &ps);
return FALSE;
}
case WM_SIZE:
{
controller->onResize (*this, getContentSize ());
break;
}
case WM_SIZING:
{
auto* newSize = reinterpret_cast<RECT*> (lParam);
RECT oldSize;
GetWindowRect (hwnd, &oldSize);
RECT clientSize;
GetClientRect (hwnd, &clientSize);
auto diffX = (newSize->right - newSize->left) - (oldSize.right - oldSize.left);
auto diffY = (newSize->bottom - newSize->top) - (oldSize.bottom - oldSize.top);
Size newClientSize = {(clientSize.right - clientSize.left),
(clientSize.bottom - clientSize.top)};
newClientSize.width += diffX;
newClientSize.height += diffY;
auto constraintSize = controller->constrainSize (*this, newClientSize);
if (constraintSize != newClientSize)
{
auto diffX2 = (oldSize.right - oldSize.left) - (clientSize.right - clientSize.left);
auto diffY2 = (oldSize.bottom - oldSize.top) - (clientSize.bottom - clientSize.top);
newSize->right = newSize->left + static_cast<LONG> (constraintSize.width + diffX2);
newSize->bottom = newSize->top + static_cast<LONG> (constraintSize.height + diffY2);
}
return TRUE;
}
case WM_CLOSE:
{
closeImmediately ();
return TRUE;
}
case WM_GETDPISCALEDSIZE:
{
inDpiChangeState = true;
auto* proposedSize = reinterpret_cast<SIZE*> (lParam);
auto newScaleFactor =
static_cast<float> (wParam) / static_cast<float> (USER_DEFAULT_SCREEN_DPI);
controller->onContentScaleFactorChanged (*this, newScaleFactor);
if (dpiChangedSize.width != 0 && dpiChangedSize.height != 0)
{
WINDOWINFO windowInfo {0};
GetWindowInfo (hwnd, &windowInfo);
RECT clientRect {};
clientRect.right = dpiChangedSize.width;
clientRect.bottom = dpiChangedSize.height;
User32Library::instance ().adjustWindowRectExForDpi (
&clientRect, windowInfo.dwStyle, false, windowInfo.dwExStyle,
static_cast<UINT> (wParam));
proposedSize->cx = clientRect.right - clientRect.left;
proposedSize->cy = clientRect.bottom - clientRect.top;
return TRUE;
}
return FALSE;
}
case WM_DPICHANGED:
{
if (inDpiChangeState)
{
auto* rect = reinterpret_cast<RECT*> (lParam);
inDpiChangeState = false;
dpiChangedSize = {};
SetWindowPos (hwnd, nullptr, rect->left, rect->top, rect->right - rect->left,
rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
controller->onContentScaleFactorChanged (*this, getContentScaleFactor ());
return 0;
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
//------------------------------------------------------------------------
void Window::closeImmediately ()
{
close ();
removeWindow (this);
controller->onClose (*this);
SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR) nullptr);
This = nullptr;
}
//------------------------------------------------------------------------
Size Window::getContentSize ()
{
RECT r;
GetClientRect (hwnd, &r);
return {r.right - r.left, r.bottom - r.top};
}
//------------------------------------------------------------------------
float Window::getContentScaleFactor () const
{
if (auto dpiForWindow = ShcoreLibrary::instance ().getDpiForWindow (hwnd))
{
return static_cast<float> (dpiForWindow->x) / static_cast<float> (USER_DEFAULT_SCREEN_DPI);
}
return 1.f;
}
//------------------------------------------------------------------------
void Window::show ()
{
controller->onContentScaleFactorChanged (*this, getContentScaleFactor ());
controller->onShow (*this);
SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOCOPYBITS | SWP_SHOWWINDOW);
}
//------------------------------------------------------------------------
void Window::close ()
{
ShowWindow (hwnd, false);
}
//------------------------------------------------------------------------
void Window::resize (Size newSize)
{
if (inDpiChangeState)
{
dpiChangedSize = newSize;
return;
}
if (getContentSize () == newSize)
return;
WINDOWINFO windowInfo {0};
GetWindowInfo (hwnd, &windowInfo);
RECT clientRect {};
clientRect.right = newSize.width;
clientRect.bottom = newSize.height;
AdjustWindowRectEx (&clientRect, windowInfo.dwStyle, false, windowInfo.dwExStyle);
SetWindowPos (hwnd, HWND_TOP, 0, 0, clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top, SWP_NOMOVE | SWP_NOCOPYBITS | SWP_NOACTIVATE);
}
//------------------------------------------------------------------------
NativePlatformWindow Window::getNativePlatformWindow () const
{
return {kPlatformTypeHWND, hwnd};
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,220 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include "public.sdk/source/vst/utility/optional.h"
#include <windows.h>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Window : public IWindow, public std::enable_shared_from_this<Window>
{
public:
static WindowPtr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance);
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance);
void show () override;
void close () override;
void resize (Size newSize) override;
Size getContentSize () override;
NativePlatformWindow getNativePlatformWindow () const override;
tresult queryInterface (const TUID /*iid*/, void** /*obj*/) override { return kNoInterface; }
void closeImmediately ();
using WindowList = std::vector<Window*>;
static WindowList getWindows ();
private:
LRESULT CALLBACK proc (UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
float getContentScaleFactor () const;
void registerWindowClass (HINSTANCE instance);
WindowPtr This;
WindowControllerPtr controller {nullptr};
HWND hwnd {nullptr};
bool inDpiChangeState {false};
Size dpiChangedSize {};
};
//------------------------------------------------------------------------
struct DynamicLibrary
{
DynamicLibrary (const char* name) { module = LoadLibraryA (name); }
~DynamicLibrary () { FreeLibrary (module); }
template <typename T>
T getProcAddress (const char* name)
{
return module ? reinterpret_cast<T> (GetProcAddress (module, name)) : nullptr;
}
private:
HMODULE module {nullptr};
};
#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE (DPI_AWARENESS_CONTEXT);
typedef enum DPI_AWARENESS {
DPI_AWARENESS_INVALID = -1,
DPI_AWARENESS_UNAWARE = 0,
DPI_AWARENESS_SYSTEM_AWARE = 1,
DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#endif
struct User32Library : DynamicLibrary
{
static User32Library& instance ()
{
static User32Library gInstance;
return gInstance;
}
bool setProcessDpiAwarenessContext (DPI_AWARENESS_CONTEXT context) const
{
if (!setProcessDpiAwarenessContextProc)
return false;
return setProcessDpiAwarenessContextProc (context);
}
bool adjustWindowRectExForDpi (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle,
UINT dpi) const
{
if (!adjustWindowRectExForDpiProc)
return false;
return adjustWindowRectExForDpiProc (lpRect, dwStyle, bMenu, dwExStyle, dpi);
}
private:
using SetProcessDpiAwarenessContextProc = BOOL (WINAPI*) (DPI_AWARENESS_CONTEXT);
using AdjustWindowRectExForDpiProc = BOOL (WINAPI*) (LPRECT, DWORD, BOOL, DWORD, UINT);
User32Library () : DynamicLibrary ("User32.dll")
{
setProcessDpiAwarenessContextProc =
getProcAddress<SetProcessDpiAwarenessContextProc> ("SetProcessDpiAwarenessContext");
adjustWindowRectExForDpiProc =
getProcAddress<AdjustWindowRectExForDpiProc> ("AdjustWindowRectExForDpi");
}
SetProcessDpiAwarenessContextProc setProcessDpiAwarenessContextProc {nullptr};
AdjustWindowRectExForDpiProc adjustWindowRectExForDpiProc {nullptr};
};
struct ShcoreLibrary : DynamicLibrary
{
static ShcoreLibrary& instance ()
{
static ShcoreLibrary gInstance;
return gInstance;
}
struct DPI
{
UINT x;
UINT y;
};
VST3::Optional<DPI> getDpiForWindow (HWND window) const
{
if (!getDpiForMonitorProc)
return {};
auto monitor = MonitorFromWindow (window, MONITOR_DEFAULTTONEAREST);
UINT x, y;
getDpiForMonitorProc (monitor, MDT_EFFECTIVE_DPI, &x, &y);
return DPI {x, y};
}
HRESULT setProcessDpiAwareness (bool perMonitor = true)
{
if (perMonitor)
{
if (User32Library::instance ().setProcessDpiAwarenessContext (
perMonitor ? DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :
DPI_AWARENESS_CONTEXT_UNAWARE))
return true;
}
if (!setProcessDpiAwarenessProc)
return S_FALSE;
return setProcessDpiAwarenessProc (perMonitor ? PROCESS_PER_MONITOR_DPI_AWARE :
PROCESS_SYSTEM_DPI_AWARE);
}
private:
enum PROCESS_DPI_AWARENESS
{
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
};
enum MONITOR_DPI_TYPE
{
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
};
using GetDpiForMonitorProc = HRESULT (WINAPI*) (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
using SetProcessDpiAwarenessProc = HRESULT (WINAPI*) (PROCESS_DPI_AWARENESS);
ShcoreLibrary () : DynamicLibrary ("Shcore.dll")
{
getDpiForMonitorProc = getProcAddress<GetDpiForMonitorProc> ("GetDpiForMonitor");
setProcessDpiAwarenessProc =
getProcAddress<SetProcessDpiAwarenessProc> ("SetProcessDpiAwareness");
}
GetDpiForMonitorProc getDpiForMonitorProc {nullptr};
SetProcessDpiAwarenessProc setProcessDpiAwarenessProc {nullptr};
};
#ifndef DPI_ENUMS_DECLARED
#define DPI_ENUMS_DECLARED
#endif // (DPI_ENUMS_DECLARED)
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/usediids.cpp
// Created by : Steinberg 09.2008
// Description : Interface symbols file
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
//#define INIT_CLASS_IID
// This macro definition modifies the behavior of DECLARE_CLASS_IID (funknown.h)
// and produces the actual symbols for all interface identifiers.
// It must be defined before including the interface headers and
// in only one source file!
//------------------------------------------------------------------------
//#define INIT_CLASS_IID
@@ -0,0 +1,75 @@
if(SMTG_ENABLE_VST3_HOSTING_EXAMPLES AND TARGET vstgui AND TARGET vstgui_standalone)
##########################################################################################
set(target VST3Inspector)
##########################################################################################
set(${target}_sources
"${SDK_ROOT}/pluginterfaces/base/coreiids.cpp"
"${SDK_ROOT}/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp"
"${SDK_ROOT}/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp"
"source/app.cpp"
"source/window.cpp"
"source/window.h"
)
set(${target}_resources
"resource/window.uidesc"
)
##########################################################################################
if(SMTG_MAC)
set(${target}_sources
${${target}_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm"
)
set_source_files_properties(
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
elseif(SMTG_WIN)
set(${target}_sources
${${target}_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp"
)
elseif(SMTG_LINUX)
set(${target}_sources
${${target}_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp"
)
endif(SMTG_MAC)
##########################################################################################
# 'vstgui_add_executable' links both targets 'vstgui' and 'vstgui_standalone' to 'VST3Inspector'
vstgui_add_executable(${target} "${${target}_sources}")
vstgui_set_target_bundle_id(${target} "com.steinberg.vstsdk.vst3inspector")
target_link_libraries(${target}
PRIVATE
sdk_hosting
)
if(NOT SMTG_MAC)
set_target_properties(${target}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<$<CONFIG:Debug>:bin/Debug>$<$<CONFIG:Release>:bin/Release>$<$<CONFIG:ReleaseLTO>:bin/ReleaseLTO>/VST3Inspector
)
endif(NOT SMTG_MAC)
vstgui_add_resources(${target} "${${target}_resources}")
vstgui_set_target_infoplist(${target} "resource/Info.plist")
target_include_directories(${target}
PRIVATE
"${SMTG_VSTGUI_SOURCE_DIR}/"
"${SDK_ROOT}"
)
if(SDK_IDE_HOSTING_EXAMPLES_FOLDER)
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
endif(SDK_IDE_HOSTING_EXAMPLES_FOLDER)
endif()
@@ -0,0 +1,18 @@
# VST 3 Inspector Application
## Introduction
As Cross-platform source code: Simple cross-platform (Win/macOS/Linux) host application, built with [VSTGUI](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/VST3Inspector.html), which scans the **VST 3** Folder, collects information from the factory about each **VST 3** plug-in and display it in its UI.
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/AudioHost.html#audiohost-application).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE}</string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.vstsdk.vst3inspector</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>VST3Inspector</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2021 Steinberg Media Technologies. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : VST3Inspector
// Filename : public.sdk/samples/vst-hosting/inspectorapp/app.cpp
// Created by : Steinberg, 01/2021
// Description : VST3 Inspector Application
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "vstgui/standalone/include/helpers/appdelegate.h"
#include "vstgui/standalone/include/helpers/windowlistener.h"
#include "vstgui/standalone/include/iapplication.h"
#include "window.h"
//------------------------------------------------------------------------
namespace VST3Inspector {
using namespace VSTGUI;
using namespace VSTGUI::Standalone;
//------------------------------------------------------------------------
struct App : Application::DelegateAdapter, WindowListenerAdapter
{
App () : Application::DelegateAdapter ({"VST3Inspector", "1.0.0", VSTGUI_STANDALONE_APP_URI}) {}
void finishLaunching () override
{
if (auto window = makeWindow ())
{
window->registerWindowListener (this);
window->show ();
}
}
void onClosed (const IWindow& window) override { IApplication::instance ().quit (); }
};
//------------------------------------------------------------------------
static Application::Init gAppDelegate (std::make_unique<App> ());
//------------------------------------------------------------------------
} // VST3Inspector
@@ -0,0 +1,500 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : VST3Inspector
// Filename : public.sdk/samples/vst-hosting/inspectorapp/window.cpp
// Created by : Steinberg, 01/2021
// Description : VST3 Inspector Application
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "vstgui/lib/cbitmap.h"
#include "vstgui/lib/platform/platformfactory.h"
#include "vstgui/standalone/include/helpers/menubuilder.h"
#include "vstgui/standalone/include/helpers/uidesc/customization.h"
#include "vstgui/standalone/include/helpers/value.h"
#include "vstgui/standalone/include/helpers/valuelistener.h"
#include "vstgui/standalone/include/helpers/windowcontroller.h"
#include "vstgui/standalone/include/ialertbox.h"
#include "vstgui/standalone/include/iapplication.h"
#include "vstgui/standalone/include/iasync.h"
#include "vstgui/standalone/include/iuidescwindow.h"
#include "vstgui/uidescription/cstream.h"
#include "vstgui/uidescription/delegationcontroller.h"
#include "vstgui/uidescription/iuidescription.h"
#include "vstgui/uidescription/uiattributes.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/moduleinfo/moduleinfo.h"
#include "public.sdk/source/vst/moduleinfo/moduleinfocreator.h"
#include "public.sdk/source/vst/moduleinfo/moduleinfoparser.h"
#include <cassert>
#include <fstream>
#include <unordered_map>
//------------------------------------------------------------------------
namespace VST3Inspector {
namespace {
//------------------------------------------------------------------------
std::string loadFile (const std::string& path)
{
std::ifstream file (path, std::ios_base::in | std::ios_base::binary);
if (!file.is_open ())
return {};
auto size = file.seekg (0, std::ios_base::end).tellg ();
file.seekg (0, std::ios_base::beg);
std::string data;
data.resize (size);
file.read (data.data (), data.size ());
if (file.bad ())
return {};
return data;
}
//------------------------------------------------------------------------
} // anonymous
using namespace VSTGUI;
using namespace VSTGUI::Standalone;
using namespace Steinberg;
//------------------------------------------------------------------------
class DummyFactory : public IPluginFactory
{
public:
static DummyFactory* instance ()
{
static DummyFactory gInstance;
return &gInstance;
}
private:
tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) override
{
*info = {};
return kResultTrue;
}
int32 PLUGIN_API countClasses () override { return 0; }
tresult PLUGIN_API getClassInfo (int32 /*index*/, PClassInfo* /*info*/) override
{
return kResultFalse;
}
tresult PLUGIN_API createInstance (FIDString /*cid*/, FIDString /*_iid*/,
void** /*obj*/) override
{
return kNoInterface;
}
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
{
QUERY_INTERFACE (_iid, obj, IPluginFactory::iid, IPluginFactory)
QUERY_INTERFACE (_iid, obj, FUnknown::iid, FUnknown)
return kNoInterface;
}
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
};
//------------------------------------------------------------------------
class InvalidModule : public VST3::Hosting::Module
{
public:
InvalidModule () { factory = VST3::Hosting::PluginFactory (DummyFactory::instance ()); }
private:
bool load (const std::string& /*path*/, std::string& /*errorDescription*/) override
{
return true;
};
};
//------------------------------------------------------------------------
struct ValueMap
{
ValuePtr addValue (const ValuePtr& value)
{
auto index = valueList.size ();
valueList.push_back (value);
valueMap.insert ({value->getID ().getString (), index});
return value;
}
ValuePtr get (const std::string& ID)
{
auto it = valueMap.find (ID);
if (it != valueMap.end ())
return valueList.at (it->second);
return nullptr;
}
template <typename T>
auto get (const std::string& ID)
{
if (auto value = get (ID))
{
return std::dynamic_pointer_cast<T> (value);
}
return std::shared_ptr<T> (nullptr);
}
const UIDesc::IModelBinding::ValueList& getValues () const { return valueList; }
private:
UIDesc::IModelBinding::ValueList valueList;
std::unordered_map<std::string, size_t> valueMap;
};
//------------------------------------------------------------------------
static constexpr auto ModulePathListID = "ModulePathList";
static constexpr auto ModulePathID = "Module::Path";
static constexpr auto FactoryVendorID = "Factory::Vendor";
static constexpr auto FactoryUrlID = "Factory::URL";
static constexpr auto FactoryEMailID = "Factory::EMail";
static constexpr auto FactoryFlagsID = "Factory::Flags";
static constexpr auto ClassInfoListID = "ClassInfoList";
static constexpr auto ClassInfoClassID = "ClassInfo::ClassID";
static constexpr auto ClassInfoCategoryID = "ClassInfo::Category";
static constexpr auto ClassInfoNameID = "ClassInfo::Name";
static constexpr auto ClassInfoVendorID = "ClassInfo::Vendor";
static constexpr auto ClassInfoVersionID = "ClassInfo::Version";
static constexpr auto ClassInfoSDKVersionID = "ClassInfo::SDKVersion";
static constexpr auto ClassInfoSubCategoriesID = "ClassInfo::SubCategories";
static constexpr auto ClassInfoCardinalityID = "ClassInfo::Cardinality";
static constexpr auto ClassInfoClassFlagsID = "ClassInfo::ClassFlags";
using namespace VST3::Hosting;
//------------------------------------------------------------------------
struct SnapshotController : DelegationController, NonAtomicReferenceCounted
{
SnapshotController (IController* parent) : DelegationController (parent) {}
~SnapshotController () noexcept override {}
CView* createView (const UIAttributes& attributes, const IUIDescription* description) override
{
if (auto viewName = attributes.getAttributeValue (IUIDescription::kCustomViewName))
{
if (*viewName == "ImageView")
{
imageView = VSTGUI::shared (new CView (CRect ()));
return imageView;
}
}
return controller->createView (attributes, description);
}
void setSnapshot (const std::string& moduleBasePath,
const ModuleInfo::SnapshotList* newSnapshot)
{
snapshot = newSnapshot ? *newSnapshot : ModuleInfo::SnapshotList ();
if (snapshot.empty ())
{
imageView->setBackground (nullptr);
imageView->setViewSize ({});
}
else
{
SharedPointer<CBitmap> bitmap;
const auto& factory = getPlatformFactory ();
for (const auto& imageDesc : snapshot)
{
if (auto pb = factory.createBitmapFromPath (
(moduleBasePath + "/" + imageDesc.path).data ()))
{
pb->setScaleFactor (imageDesc.scaleFactor);
if (bitmap)
bitmap->addBitmap (pb);
else
bitmap = VSTGUI::owned (new CBitmap (pb));
}
}
imageView->setBackground (bitmap);
if (bitmap)
{
auto viewSize = imageView->getViewSize ();
viewSize.setSize (bitmap->getSize ());
imageView->setViewSize (viewSize);
}
}
}
SharedPointer<CView> imageView;
ModuleInfo::SnapshotList snapshot;
};
//------------------------------------------------------------------------
struct WindowController : WindowControllerAdapter,
UIDesc::CustomizationAdapter,
UIDesc::IModelBinding,
MenuBuilderAdapter,
ValueListenerAdapter
{
WindowController ()
{
values.addValue (Value::makeStringListValue (ModulePathListID, {"", ""}))
->registerListener (this);
// Module Values
values.addValue (Value::makeStringValue (ModulePathID, ""));
// Factory Values
values.addValue (Value::makeStringValue (FactoryVendorID, ""));
values.addValue (Value::makeStringValue (FactoryUrlID, ""));
values.addValue (Value::makeStringValue (FactoryEMailID, ""));
values.addValue (Value::makeStringValue (FactoryFlagsID, ""));
values.addValue (Value::makeStringListValue (ClassInfoListID, {"", ""}))
->registerListener (this);
// Class Info Values
values.addValue (Value::makeStringValue (ClassInfoClassID, ""));
values.addValue (Value::makeStringValue (ClassInfoCategoryID, ""));
values.addValue (Value::makeStringValue (ClassInfoNameID, ""));
values.addValue (Value::makeStringValue (ClassInfoVendorID, ""));
values.addValue (Value::makeStringValue (ClassInfoVersionID, ""));
values.addValue (Value::makeStringValue (ClassInfoSDKVersionID, ""));
values.addValue (Value::makeStringValue (ClassInfoSubCategoriesID, ""));
values.addValue (Value::makeStringValue (ClassInfoCardinalityID, ""));
values.addValue (Value::makeStringValue (ClassInfoClassFlagsID, ""));
Async::schedule (Async::backgroundQueue (), [this] () {
auto modulePaths = Module::getModulePaths ();
Async::schedule (Async::mainQueue (),
[this, paths = std::move (modulePaths)] () mutable {
setModulePaths (std::move (paths));
});
});
}
static Optional<std::string> lastPathComponent (const std::string& path)
{
#if SMTG_WINDOWS
static constexpr auto pathSeparator = '\\';
#else
static constexpr auto pathSeparator = '/';
#endif
size_t sepPos = path.find_last_of (pathSeparator);
if (sepPos == std::string::npos)
return {};
return {path.substr (sepPos + 1)};
}
void setModulePaths (Module::PathList&& pathList)
{
modulePathList = std::move (pathList);
std::sort (modulePathList.begin (), modulePathList.end (),
[] (const auto& lhs, const auto& rhs) {
auto lhsName = lastPathComponent (lhs);
auto rhsName = lastPathComponent (rhs);
if (lhsName && rhsName)
{
std::for_each (lhsName->begin (), lhsName->end (),
[] (auto& c) { c = std::tolower (c); });
std::for_each (rhsName->begin (), rhsName->end (),
[] (auto& c) { c = std::tolower (c); });
return *lhsName < *rhsName;
}
return lhs < rhs;
});
moduleInfos.clear ();
moduleInfos.resize (modulePathList.size ());
IStringListValue::StringList nameList;
for (const auto& path : modulePathList)
{
if (auto name = lastPathComponent (path))
nameList.emplace_back (*name);
else
nameList.emplace_back (path);
}
if (auto value = values.get<IStringListValue> (ModulePathListID))
value->updateStringList (nameList);
if (auto value = values.get (ModulePathListID))
Value::performSingleEdit (*value, 0.);
}
void onEndEdit (IValue& value) override
{
if (value.getID () == ModulePathListID)
{
onModuleSelection (static_cast<uint32_t> (
value.getConverter ().normalizedToPlain (value.getValue ())));
}
if (value.getID () == ClassInfoListID)
{
onClassInfoSelection (static_cast<uint32_t> (
value.getConverter ().normalizedToPlain (value.getValue ())));
}
}
void setStringValue (const std::string& valueID, const std::string& string)
{
auto value = values.get (valueID);
if (value)
Value::performStringValueEdit (*value, UTF8String (string));
}
std::string createFlagsString (int32_t flags) const
{
std::string flagsString = "0b";
for (int32_t i = 31; i >= 0; --i)
{
if (flags & (1 << i))
flagsString += "1";
else
flagsString += "0";
}
return flagsString;
}
std::string createSubCategoryString (const ModuleInfo::ClassInfo& info) const
{
std::string result;
for (const auto& cat : info.subCategories)
{
if (result.empty () == false)
result += "|";
result += cat;
}
return result;
}
void onClassInfoSelection (uint32_t index)
{
if (index >= moduleInfos[currentSelectedModule].classes.size ())
{
setStringValue (ClassInfoClassID, "");
setStringValue (ClassInfoCategoryID, "");
setStringValue (ClassInfoNameID, "");
setStringValue (ClassInfoVendorID, "");
setStringValue (ClassInfoVersionID, "");
setStringValue (ClassInfoSDKVersionID, "");
setStringValue (ClassInfoSubCategoriesID, "");
setStringValue (ClassInfoCardinalityID, "");
setStringValue (ClassInfoClassFlagsID, "");
if (snapShotController)
snapShotController->setSnapshot ("", nullptr);
return;
}
const auto& cl = moduleInfos[currentSelectedModule].classes[index];
setStringValue (ClassInfoClassID, cl.cid);
setStringValue (ClassInfoCategoryID, cl.category);
setStringValue (ClassInfoNameID, cl.name);
setStringValue (ClassInfoVendorID, cl.vendor);
setStringValue (ClassInfoVersionID, cl.version);
setStringValue (ClassInfoSDKVersionID, cl.sdkVersion);
setStringValue (ClassInfoSubCategoriesID, createSubCategoryString (cl));
if (cl.cardinality == Steinberg::PClassInfo::ClassCardinality::kManyInstances)
setStringValue (ClassInfoCardinalityID, "");
else
setStringValue (ClassInfoCardinalityID, std::to_string (cl.cardinality));
setStringValue (ClassInfoClassFlagsID, createFlagsString (cl.flags));
if (snapShotController)
snapShotController->setSnapshot (modulePathList[currentSelectedModule], &cl.snapshots);
}
void updateModuleInfo (uint32_t index)
{
const auto& modulePath = modulePathList[index];
if (auto moduleInfoPath = Module::getModuleInfoPath (modulePath))
{
auto moduleJson = loadFile (*moduleInfoPath);
if (auto moduleInfo = ModuleInfoLib::parseJson (moduleJson, nullptr))
{
moduleInfos[index] = *moduleInfo;
}
}
else
{
std::string errorDesc;
if (auto module = Module::create (modulePath, errorDesc))
{
moduleInfos[index] = ModuleInfoLib::createModuleInfo (*module.get (), true);
}
}
}
void onModuleSelection (uint32_t index)
{
if (index >= modulePathList.size ())
return;
currentSelectedModule = index;
if (moduleInfos[index].name.empty ())
{
updateModuleInfo (index);
}
const auto& moduleInfo = moduleInfos[index];
const auto& modulePath = modulePathList[index];
setStringValue (ModulePathID, modulePath);
setStringValue (FactoryVendorID, moduleInfo.factoryInfo.vendor);
setStringValue (FactoryUrlID, moduleInfo.factoryInfo.url);
setStringValue (FactoryEMailID, moduleInfo.factoryInfo.email);
setStringValue (FactoryFlagsID, createFlagsString (moduleInfo.factoryInfo.flags));
IStringListValue::StringList classInfoList;
for (const auto& classInfo : moduleInfo.classes)
{
classInfoList.emplace_back (classInfo.name);
}
if (auto value = values.get<IStringListValue> (ClassInfoListID))
{
value->updateStringList (classInfoList);
}
// select first class info
if (auto value = values.get (ClassInfoListID))
Value::performSingleEdit (*value, 0.);
}
const ValueList& getValues () const override { return values.getValues (); }
IController* createController (const UTF8StringView& name, IController* parent,
const IUIDescription* /*uiDesc*/) override
{
if (name == "SnapshotViewController")
{
snapShotController = VSTGUI::shared (new SnapshotController (parent));
return snapShotController;
}
return nullptr;
}
bool showCommandInMenu (const Interface& /*context*/, const Command& /*cmd*/) const override
{
return false;
}
private:
ValueMap values;
ValuePtr modulePathListValue;
SharedPointer<SnapshotController> snapShotController;
using ModuleInfoList = std::vector<ModuleInfo>;
Module::PathList modulePathList;
ModuleInfoList moduleInfos;
uint32_t currentSelectedModule {0};
};
//------------------------------------------------------------------------
WindowPtr makeWindow ()
{
auto controller = std::make_shared<WindowController> ();
UIDesc::Config config;
config.uiDescFileName = "window.uidesc";
config.viewName = "MainWindow";
config.windowConfig.autoSaveFrameName = "MainWindow";
config.windowConfig.type = WindowType::Document;
config.windowConfig.style = WindowStyle ().border ().close ().centered ().size ();
config.windowConfig.title = "VST3 Inspector";
config.customization = controller;
config.modelBinding = controller;
return UIDesc::makeWindow (config);
}
//------------------------------------------------------------------------
} // VST3Inspector
@@ -0,0 +1,27 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : VST3Inspector
// Filename : public.sdk/samples/vst-hosting/inspectorapp/window.h
// Created by : Steinberg, 01/2021
// Description : VST3 Inspector Application
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "vstgui/standalone/include/iwindow.h"
//------------------------------------------------------------------------
namespace VST3Inspector {
//------------------------------------------------------------------------
VSTGUI::Standalone::WindowPtr makeWindow ();
//------------------------------------------------------------------------
} // VST3Inspector
@@ -0,0 +1,159 @@
set(validator_sources
${SDK_ROOT}/public.sdk/source/common/memorystream.cpp
${SDK_ROOT}/public.sdk/source/main/moduleinit.cpp
${SDK_ROOT}/public.sdk/source/main/moduleinit.h
${SDK_ROOT}/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp
${SDK_ROOT}/public.sdk/source/vst/moduleinfo/moduleinfoparser.h
${SDK_ROOT}/public.sdk/source/vst/hosting/test/connectionproxytest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/test/eventlisttest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/test/hostclassestest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/test/parameterchangestest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/test/pluginterfacesupporttest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/test/processdatatest.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/busactivation.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/busactivation.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/busconsistency.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/busconsistency.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/businvalidindex.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/businvalidindex.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/checkaudiobusarrangement.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/checkaudiobusarrangement.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/scanbusses.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/scanbusses.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/sidechainarrangement.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/bus/sidechainarrangement.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/editorclasses.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/editorclasses.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/midilearn.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/midilearn.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/midimapping.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/midimapping.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/plugcompat.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/plugcompat.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/scanparameters.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/scanparameters.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/suspendresume.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/suspendresume.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/terminit.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/general/terminit.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/noteexpression/keyswitch.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/noteexpression/keyswitch.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/noteexpression/noteexpression.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/noteexpression/noteexpression.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/automation.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/automation.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/process.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/process.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processcontextrequirements.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processcontextrequirements.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processformat.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processformat.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processinputoverwriting.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processinputoverwriting.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processtail.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processtail.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processthreaded.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/processthreaded.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/silenceflags.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/silenceflags.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/silenceprocessing.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/silenceprocessing.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/speakerarrangement.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/speakerarrangement.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/variableblocksize.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/processing/variableblocksize.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/bypasspersistence.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/bypasspersistence.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/invalidstatetransition.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/invalidstatetransition.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/repeatidenticalstatetransition.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/repeatidenticalstatetransition.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/validstatetransition.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/state/validstatetransition.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/testbase.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/testbase.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/checkunitstructure.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/checkunitstructure.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/scanprograms.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/scanprograms.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/scanunits.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/unit/scanunits.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/vststructsizecheck.h
${SDK_ROOT}/public.sdk/source/vst/testsuite/vsttestsuite.cpp
${SDK_ROOT}/public.sdk/source/vst/testsuite/vsttestsuite.h
${SDK_ROOT}/public.sdk/source/vst/utility/testing.cpp
${SDK_ROOT}/public.sdk/source/vst/utility/testing.h
source/main.cpp
source/usediids.cpp
source/validator.cpp
source/validator.h
source/testsuite.h
)
if(SMTG_MAC)
set(validator_sources
${validator_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm
)
set_source_files_properties(
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
elseif(SMTG_LINUX)
set(validator_sources
${validator_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp
)
elseif(SMTG_WIN)
set(validator_sources
${validator_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp
)
endif(SMTG_MAC)
set(target validator)
add_executable(${target} ${validator_sources})
if(SDK_IDE_HOSTING_EXAMPLES_FOLDER)
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
endif(SDK_IDE_HOSTING_EXAMPLES_FOLDER)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
)
smtg_target_codesign(${target})
smtg_target_setup_universal_binary(${target})
if(APPLE AND NOT XCODE)
find_library(COCOA_FRAMEWORK Cocoa)
target_link_libraries(${target}
PRIVATE
${COCOA_FRAMEWORK}
)
endif(APPLE AND NOT XCODE)
#####################################################
if(SMTG_RUN_VST_VALIDATOR)
####################
# Workaround for Xcode 15 not allowing to add adhoc_codesign linker setting
####################
if(SMTG_MAC AND XCODE_VERSION VERSION_GREATER_EQUAL 15)
add_custom_command(
TARGET ${target} POST_BUILD
COMMENT "[SMTG] Adhoc codesign workaround for Xcode 15"
COMMAND codesign --force --verbose --sign -
"$<TARGET_FILE:${target}>"
)
endif()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND $<TARGET_FILE:${target}> "-selftest"
)
endif(SMTG_RUN_VST_VALIDATOR)
@@ -0,0 +1,18 @@
# Validator
## Introduction
Validator is a small command line host application which can be used to check your plug-in for **VST 3** conformity.
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/Validator.html).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,86 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Validator
// Filename : public.sdk/samples/vst-hosting/validator/source/main.cpp
// Created by : Steinberg, 04/2005
// Description : main entry point
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "validator.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
void* moduleHandle = nullptr;
extern bool InitModule ();
extern bool DeinitModule ();
//------------------------------------------------------------------------
int run (int argc, char* argv[])
{
InitModule ();
auto result = Steinberg::Vst::Validator (argc, argv).run ();
DeinitModule ();
return result;
}
//------------------------------------------------------------------------
#if SMTG_OS_WINDOWS
//------------------------------------------------------------------------
using Utf8String = std::string;
//------------------------------------------------------------------------
using Utf8Args = std::vector<Utf8String>;
Utf8Args toUtf8Args (int argc, wchar_t* wargv[])
{
Utf8Args utf8Args;
for (int i = 0; i < argc; i++)
{
auto str = reinterpret_cast<const Steinberg::Vst::TChar*> (wargv[i]);
utf8Args.push_back (Steinberg::Vst::StringConvert::convert (str));
}
return utf8Args;
}
//------------------------------------------------------------------------
using Utf8ArgPtrs = std::vector<char*>;
Utf8ArgPtrs toUtf8ArgPtrs (Utf8Args& utf8Args)
{
Utf8ArgPtrs utf8ArgPtrs;
for (auto& el : utf8Args)
{
utf8ArgPtrs.push_back (el.data ());
}
return utf8ArgPtrs;
}
//------------------------------------------------------------------------
int wmain (int argc, wchar_t* wargv[])
{
Utf8Args utf8Args = toUtf8Args (argc, wargv);
Utf8ArgPtrs utf8ArgPtrs = toUtf8ArgPtrs (utf8Args);
char** argv = &(utf8ArgPtrs.at (0));
return run (argc, argv);
}
#else
//------------------------------------------------------------------------
int main (int argc, char* argv[])
{
return run (argc, argv);
}
#endif // SMTG_OS_WINDOWS
@@ -0,0 +1,109 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : Validator
// Filename : public.sdk/samples/vst-hosting/validator/source/testsuite.h
// Created by : Steinberg, 05/2019
// Description : VST 3 Plug-in Validator test suite class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "base/source/fobject.h"
#include "pluginterfaces/test/itest.h"
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
// TestSuite
//------------------------------------------------------------------------
class TestSuite : public ITestSuite, public FObject
{
public:
TestSuite (FIDString _name) : name (_name) {}
tresult PLUGIN_API addTest (FIDString _name, ITest* test) SMTG_OVERRIDE
{
tests.push_back (IPtr<Test> (NEW Test (_name, test), false));
return kResultTrue;
}
tresult PLUGIN_API addTestSuite (FIDString _name, ITestSuite* testSuite) SMTG_OVERRIDE
{
testSuites.push_back (std::make_pair (_name, testSuite));
return kResultTrue;
}
tresult PLUGIN_API setEnvironment (ITest* /*environment*/) SMTG_OVERRIDE
{
return kNotImplemented;
}
int32 getTestCount () const { return static_cast<int32> (tests.size ()); }
tresult getTest (int32 index, ITest*& _test, std::string& _name) const
{
if (auto test = tests.at (index))
{
_test = test->test;
_name = test->name;
return kResultTrue;
}
return kResultFalse;
}
tresult getTestSuite (int32 index, ITestSuite*& testSuite, std::string& _name) const
{
if (index < 0 || index >= int32 (testSuites.size ()))
return kInvalidArgument;
const auto& ts = testSuites[index];
_name = ts.first;
testSuite = ts.second;
return kResultTrue;
}
ITestSuite* getTestSuite (FIDString _name) const
{
for (const auto& testSuite : testSuites)
{
if (testSuite.first == _name)
return testSuite.second;
}
return nullptr;
}
const std::string& getName () const { return name; }
OBJ_METHODS (TestSuite, FObject)
REFCOUNT_METHODS (FObject)
DEF_INTERFACES_1 (ITestSuite, FObject)
protected:
class Test : public FObject
{
public:
Test (FIDString _name, ITest* _test) : name (_name), test (_test) {}
std::string name;
IPtr<ITest> test;
};
std::string name;
std::vector<IPtr<Test>> tests;
using TestSuitePair = std::pair<std::string, IPtr<ITestSuite>>;
using TestSuiteVector = std::vector<TestSuitePair>;
TestSuiteVector testSuites;
};
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Validator
// Filename : public.sdk/samples/vst-hosting/validator/source/usediids.cpp
// Created by : Steinberg 09.2008
// Description : Interface symbols file
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
//#define INIT_CLASS_IID
// This macro definition modifies the behavior of DECLARE_CLASS_IID (funknown.h)
// and produces the actual symbols for all interface identifiers.
// It must be defined before including the interface headers and
// in only one source file!
//------------------------------------------------------------------------
//#define INIT_CLASS_IID
#include "pluginterfaces/base/ipluginbase.h"
#include "pluginterfaces/test/itest.h"
namespace Steinberg {
DEF_CLASS_IID (ITestResult)
}
@@ -0,0 +1,837 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : Validator
// Filename : public.sdk/samples/vst-hosting/validator/source/validator.cpp
// Created by : Steinberg, 04/2005
// Description : VST 3 Plug-in Validator class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "validator.h"
#include "testsuite.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/vst/testsuite/general/plugcompat.h"
#include "public.sdk/source/vst/testsuite/vststructsizecheck.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "public.sdk/source/vst/utility/testing.h"
#include "base/source/fcommandline.h"
#include "pluginterfaces/base/iplugincompatibility.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstcomponent.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivsthostapplication.h"
#include "pluginterfaces/vst/ivstunits.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iplugview.h"
#if SMTG_OS_WINDOWS
#include <conio.h>
#include <windows.h>
#endif
#include <array>
#include <cstdio>
#include <iostream>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
constexpr auto SEPARATOR = "-------------------------------------------------------------\n";
constexpr auto VALIDATOR_INFO = kVstVersionString
" Plug-in Validator\n"
"Program by Steinberg (Built on " __DATE__ ")\n";
//------------------------------------------------------------------------
bool filterClassCategory (FIDString category, FIDString classCategory)
{
return strcmp (category, classCategory) == 0;
}
//------------------------------------------------------------------------
void printAllInstalledPlugins (std::ostream* os)
{
if (!os)
return;
*os << "Searching installed Plug-ins...\n";
os->flush ();
auto paths = VST3::Hosting::Module::getModulePaths ();
if (paths.empty ())
{
*os << "No Plug-ins found.\n";
return;
}
for (const auto& path : paths)
{
*os << path << "\n";
}
}
//------------------------------------------------------------------------
void printAllSnapshots (std::ostream* os)
{
if (!os)
return;
*os << "Searching installed Plug-ins...\n";
auto paths = VST3::Hosting::Module::getModulePaths ();
if (paths.empty ())
{
*os << "No Plug-ins found.\n";
return;
}
for (const auto& path : paths)
{
auto snapshots = VST3::Hosting::Module::getSnapshots (path);
if (snapshots.empty ())
{
*os << "No snapshots in " << path << "\n";
continue;
}
for (auto& snapshot : snapshots)
{
for (auto& desc : snapshot.images)
{
*os << "Snapshot : " << desc.path << "[" << desc.scaleFactor << "x]\n";
}
}
}
}
//------------------------------------------------------------------------
void printFactoryInfo (const VST3::Hosting::PluginFactory& factory, std::ostream* os)
{
if (os)
{
*os << "* Scanning classes...\n\n";
auto factoryInfo = factory.info ();
*os << " Factory Info:\n\tvendor = " << factoryInfo.vendor ()
<< "\n\turl = " << factoryInfo.url () << "\n\temail = " << factoryInfo.email ()
<< "\n\n";
//---print all included plug-ins---------------
uint32 i = 0;
for (auto& classInfo : factory.classInfos ())
{
*os << " Class Info " << i << ":\n\tname = " << classInfo.name ()
<< "\n\tcategory = " << classInfo.category ()
<< "\n\tsubCategories = " << classInfo.subCategoriesString ()
<< "\n\tversion = " << classInfo.version ()
<< "\n\tsdkVersion = " << classInfo.sdkVersion ()
<< "\n\tcid = " << classInfo.ID ().toString () << "\n\n";
++i;
}
}
}
//------------------------------------------------------------------------
void checkModuleSnapshots (const VST3::Hosting::Module::Ptr& module, std::ostream* infoStream)
{
if (infoStream)
*infoStream << "* Checking snapshots...\n\n";
auto snapshots = VST3::Hosting::Module::getSnapshots (module->getPath ());
if (snapshots.empty ())
{
if (infoStream)
*infoStream << "Info: No snapshots in Bundle.\n\n";
}
else
{
for (auto& classInfo : module->getFactory ().classInfos ())
{
if (!filterClassCategory (kVstAudioEffectClass, classInfo.category ().data ()))
continue;
bool found = false;
for (auto& snapshot : snapshots)
{
if (snapshot.uid == classInfo.ID ())
{
found = true;
if (infoStream)
{
*infoStream << "Found snapshots for '" << classInfo.name () << "'\n";
for (auto& image : snapshot.images)
*infoStream << " - " << image.path << " [" << image.scaleFactor
<< "x]\n";
*infoStream << "\n";
}
break;
}
}
if (!found)
{
if (infoStream)
{
*infoStream << "Info: No snapshot for '" << classInfo.name ()
<< "' in Bundle.\n\n";
}
}
}
}
}
#if SMTG_OS_LINUX
//------------------------------------------------------------------------
struct DummyRunLoop : U::ImplementsNonDestroyable<U::Directly<Steinberg::Linux::IRunLoop>>
{
using IEventHandler = Steinberg::Linux::IEventHandler;
using ITimerHandler = Steinberg::Linux::ITimerHandler;
using FileDescriptor = Steinberg::Linux::FileDescriptor;
using TimerInterval = Steinberg::Linux::TimerInterval;
static Steinberg::Linux::IRunLoop& instance ()
{
static DummyRunLoop drl;
return drl;
}
tresult PLUGIN_API registerEventHandler (IEventHandler* handler, FileDescriptor fd) override
{
return kNotImplemented;
}
tresult PLUGIN_API unregisterEventHandler (IEventHandler* handler) override
{
return kNotImplemented;
}
tresult PLUGIN_API registerTimer (ITimerHandler* handler, TimerInterval milliseconds) override
{
return kNotImplemented;
}
tresult PLUGIN_API unregisterTimer (ITimerHandler* handler) override { return kNotImplemented; }
};
#endif
//------------------------------------------------------------------------
//-- Options
constexpr auto optHelp = "help";
constexpr auto optVersion = "version";
constexpr auto optLocalInstance = "l";
constexpr auto optSuiteName = "suite";
constexpr auto optExtensiveTests = "e";
constexpr auto optQuiet = "q";
constexpr auto optTestComponentPath = "test-component";
constexpr auto optListInstalledPlugIns = "list";
constexpr auto optListPlugInSnapshots = "snapshots";
constexpr auto optCID = "cid";
constexpr auto optSelftest = "selftest";
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
// Validator
//------------------------------------------------------------------------
Validator::Validator (int argc, char* argv[]) : argc (argc), argv (argv)
{
infoStream = &std::cout;
errorStream = &std::cout;
errorStream->setf (std::ios::unitbuf);
mPlugInterfaceSupport = owned (NEW PlugInterfaceSupport);
PluginContextFactory::instance ().setPluginContext (this->unknownCast ());
TestingPluginContext::set (this->unknownCast ());
}
//------------------------------------------------------------------------
Validator::~Validator ()
{
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API Validator::queryInterface (const char* _iid, void** obj)
{
QUERY_INTERFACE (_iid, obj, IHostApplication::iid, IHostApplication)
QUERY_INTERFACE (_iid, obj, ITestResult::iid, ITestResult)
if (mPlugInterfaceSupport && mPlugInterfaceSupport->queryInterface (_iid, obj) == kResultTrue)
return ::Steinberg::kResultOk;
return FObject::queryInterface (_iid, obj);
}
//------------------------------------------------------------------------
void PLUGIN_API Validator::addErrorMessage (const tchar* msg)
{
if (errorStream)
{
auto str = StringConvert::convert (msg);
if (addErrorWarningTextToOutput)
*errorStream << "ERROR: " << str << "\n";
else
*errorStream << str << "\n";
}
}
//------------------------------------------------------------------------
void PLUGIN_API Validator::addMessage (const tchar* msg)
{
if (infoStream)
{
auto str = StringConvert::convert (msg);
if (addErrorWarningTextToOutput)
*infoStream << "Info: " << str << "\n";
else
*infoStream << str << "\n";
}
}
//------------------------------------------------------------------------
tresult PLUGIN_API Validator::getName (String128 name)
{
StringConvert::convert ("vstvalidator", name, 127);
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API Validator::createInstance (TUID cid, TUID iid, void** obj)
{
FUID classID = FUID::fromTUID (cid);
FUID interfaceID = FUID::fromTUID (iid);
if (classID == IMessage::iid && interfaceID == IMessage::iid)
{
*obj = new HostMessage;
return kResultTrue;
}
else if (classID == IAttributeList::iid && interfaceID == IAttributeList::iid)
{
if (auto al = HostAttributeList::make ())
{
*obj = al.take ();
return kResultTrue;
}
return kOutOfMemory;
}
*obj = nullptr;
return kResultFalse;
}
//------------------------------------------------------------------------
int Validator::run ()
{
// defaults
bool useGlobalInstance = true;
bool useExtensiveTests = false;
std::string testSuiteName;
// parse command line
CommandLine::Descriptions desc;
CommandLine::VariablesMap valueMap;
CommandLine::FilesVector files;
using Description = CommandLine::Description;
desc.addOptions (
kVstVersionString " Plug-in Validator",
{{optHelp, "Print help", Description::kBool},
{optVersion, "Print version", Description::kBool},
{optLocalInstance, "Use local instance per test", Description::kBool},
{optSuiteName, "[name] Only run a special test suite", Description::kString},
{optExtensiveTests, "Run extensive tests [may take a long time]", Description::kBool},
{optQuiet, "Only print errors", Description::kBool},
{optCID, "Only test processor with specified class ID", Description::kString},
{optTestComponentPath,
"[path] Path to an additional component which includes custom tests",
Description::kString},
{optListInstalledPlugIns, "Show all installed Plug-Ins", Description::kBool},
{optSelftest, "Run a selftest", Description::kBool},
{optListPlugInSnapshots, "List snapshots from all installed Plug-Ins",
Description::kBool}});
CommandLine::parse (argc, argv, desc, valueMap, &files);
if (valueMap.count (optVersion))
{
std::cout << VALIDATOR_INFO;
return 0;
}
else if (valueMap.count (optListInstalledPlugIns))
{
printAllInstalledPlugins (infoStream);
return 0;
}
else if (valueMap.count (optListPlugInSnapshots))
{
printAllSnapshots (infoStream);
return 0;
}
else if (valueMap.count (optSelftest))
{
addErrorWarningTextToOutput = false;
auto testFactoryInstance = owned (createTestFactoryInstance (nullptr));
if (auto testFactory = U::cast<ITestFactory> (testFactoryInstance))
{
std::cout << "Running validator selftest:\n\n";
IPtr<TestSuite> testSuite = owned (new TestSuite (""));
if (testFactory->createTests (nullptr, testSuite) == kResultTrue)
runTestSuite (testSuite, nullptr);
std::cout << "Executed " << (numTestsFailed + numTestsPassed) << " Tests.\n";
std::cout << numTestsFailed << " failed test(s).\n";
return numTestsFailed == 0 ? 0 : 1;
}
return 1;
}
else if (valueMap.hasError () || valueMap.count (optHelp) || files.empty ())
{
std::cout << "\n" << desc << "\n";
std::cout << "Usage: vstvalidator [options] vst3module\n\n";
return 1;
}
if (valueMap.count (optLocalInstance))
useGlobalInstance = false;
if (valueMap.count (optExtensiveTests))
useExtensiveTests = true;
if (valueMap.count (optQuiet))
infoStream = nullptr;
if (valueMap.count (optSuiteName))
testSuiteName = valueMap[optSuiteName];
VST3::Optional<VST3::UID> testProcessor;
if (valueMap.count (optCID))
testProcessor = VST3::UID::fromString (valueMap[optCID]);
std::string customTestComponentPath;
if (valueMap.count (optTestComponentPath))
customTestComponentPath = valueMap[optTestComponentPath];
auto globalFailure = false;
for (auto& path : files)
{
#if SMTG_OS_WINDOWS
// TODO: Impl
#else
// if path is not absolute, create one
if (path[0] != '/')
{
auto realPath = realpath (path.data (), NULL);
if (realPath)
{
path.assign (realPath);
free (realPath);
}
}
if (customTestComponentPath.empty () == false && customTestComponentPath[0] != '/')
{
auto realPath = realpath (customTestComponentPath.data (), NULL);
if (realPath)
{
customTestComponentPath.assign (realPath);
free (realPath);
}
}
#endif
//---load VST module-----------------
if (infoStream)
*infoStream << "* Loading module...\n\n\t" << path << "\n\n";
std::string error;
auto module = Module::create (path, error);
if (!module)
{
*errorStream << "Invalid Module!\n";
if (!error.empty ())
*errorStream << error << "\n";
return -1;
}
#if SMTG_OS_LINUX
module->getFactory ().setHostContext (&DummyRunLoop::instance ());
#endif
testModule (module, {useGlobalInstance, useExtensiveTests, customTestComponentPath,
testSuiteName, std::move (testProcessor)});
if (numTestsFailed > 0)
globalFailure = true;
}
return globalFailure ? -1 : 0;
}
//------------------------------------------------------------------------
void Validator::testModule (Module::Ptr module, const ModuleTestConfig& config)
{
using TestFactoryMap = std::map<std::string, IPtr<ITestFactory>>;
using PlugProviderVector = std::vector<IPtr<PlugProvider>>;
numTestsFailed = numTestsPassed = 0;
//---check bundle structure--------------
std::string errorStr;
if (infoStream)
*infoStream << "* Check valid bundle structure...\n";
if (Module::validateBundleStructure (module->getPath (), errorStr) == false)
{
++numTestsFailed;
if (errorStream)
*errorStream << "Invalid bundle structure: " << errorStr << "\n";
}
if (infoStream)
*infoStream << "\n";
auto factory = module->getFactory ();
printFactoryInfo (module->getFactory (), infoStream);
//---check for snapshots-----------------
checkModuleSnapshots (module, infoStream);
Module::Ptr testModule;
PlugProviderVector plugProviders;
TestFactoryMap testFactories;
IPtr<IPluginCompatibility> plugCompatibility;
auto testSuite = owned (new TestSuite ("Tests"));
//---create tests---------------
if (infoStream)
*infoStream << "* Creating tests...\n\n";
for (auto& classInfo : factory.classInfos ())
{
if (filterClassCategory (kVstAudioEffectClass, classInfo.category ().data ()))
{
if (!config.testProcessor || *config.testProcessor == classInfo.ID ())
{
auto plugProvider =
owned (new PlugProvider (factory, classInfo, config.useGlobalInstance));
if (plugProvider)
{
auto tests = createTests (plugProvider, classInfo.name ().data (),
config.useExtensiveTests);
testSuite->addTestSuite (classInfo.name ().data (), tests);
plugProviders.emplace_back (plugProvider);
}
}
}
else if (filterClassCategory (kTestClass, classInfo.category ().data ()))
{ // gather test factories supplied by the plug-in
if (auto testFactory = factory.createInstance<ITestFactory> (classInfo.ID ()))
{
testFactories.insert (std::make_pair (classInfo.name ().data (), testFactory));
}
}
else if (filterClassCategory (kPluginCompatibilityClass, classInfo.category ().data ()))
{
if (plugCompatibility)
{
if (errorStream)
{
*errorStream
<< "Error: Factory contains multiple Plugin Compatibility classes.\n";
}
++numTestsFailed;
}
plugCompatibility = factory.createInstance<IPluginCompatibility> (classInfo.ID ());
if (!plugCompatibility)
{
if (errorStream)
{
*errorStream << "Error: Failed creating IPluginCompatibility instance.\n";
}
}
}
}
// now check testModule if supplied
if (config.customTestComponentPath.empty () == false)
{
std::string error;
testModule = Module::create (config.customTestComponentPath, error);
if (testModule)
{
const auto& _factory = testModule->getFactory ();
for (const auto& classInfo : _factory.classInfos ())
{
if (filterClassCategory (kTestClass, classInfo.category ().data ()))
{ // gather test factories supplied by the plug-in
if (auto testFactory = _factory.createInstance<ITestFactory> (classInfo.ID ()))
{
testFactories.insert (
std::make_pair (classInfo.name ().data (), testFactory));
}
}
}
}
else if (errorStream)
{
*errorStream << "Could not create custom test component ["
<< config.customTestComponentPath << "]\n";
}
}
if (infoStream && !testFactories.empty ())
*infoStream << "* Creating Plug-in supplied tests...\n\n";
// create plug-in supplied tests
for (const auto& item : testFactories)
{
for (const auto& plugProvider : plugProviders)
{
IPtr<TestSuite> plugTestSuite = owned (new TestSuite (item.first.data ()));
if (item.second->createTests (plugProvider->unknownCast (), plugTestSuite) ==
kResultTrue)
{
testSuite->addTestSuite (plugTestSuite->getName ().data (), plugTestSuite);
}
}
}
testFactories.clear ();
//---run tests---------------------------
if (infoStream)
*infoStream << "* Running tests...\n\n";
runTestSuite (testSuite,
config.testSuiteName.empty () ? nullptr : config.testSuiteName.data ());
if (plugCompatibility)
{
if (!checkPluginCompatibility (module, plugCompatibility, errorStream))
++numTestsFailed;
}
if (infoStream)
{
*infoStream << SEPARATOR;
*infoStream << "Result: " << numTestsPassed << " tests passed, " << numTestsFailed
<< " tests failed\n";
*infoStream << SEPARATOR;
}
}
//------------------------------------------------------------------------
namespace { // anonymous
//------------------------------------------------------------------------
template <typename T, typename... Args>
void createTest (ITestSuite* parent, ITestPlugProvider* plugProvider, Args&&... arguments)
{
auto test = owned (new T (plugProvider, std::forward<Args> (arguments)...));
parent->addTest (test->getName (), test);
}
//------------------------------------------------------------------------
void createSpeakerArrangementTest (ITestSuite* parent, ITestPlugProvider* plugProvider,
SymbolicSampleSizes sampleSize, SpeakerArrangement inSpArr,
SpeakerArrangement outSpArr)
{
createTest<SpeakerArrangementTest> (parent, plugProvider, sampleSize, inSpArr, outSpArr);
}
//------------------------------------------------------------------------
void createPrecisionTests (ITestSuite* parent, ITestPlugProvider* plugProvider,
SymbolicSampleSizes sampleSize, bool extensive)
{
createTest<ProcessTest> (parent, plugProvider, sampleSize);
createTest<ProcessThreadTest> (parent, plugProvider, sampleSize);
createTest<SilenceFlagsTest> (parent, plugProvider, sampleSize);
createTest<SilenceProcessingTest> (parent, plugProvider, sampleSize);
createTest<FlushParamTest> (parent, plugProvider, sampleSize);
createTest<FlushParamTest2> (parent, plugProvider, sampleSize);
createTest<FlushParamTest3> (parent, plugProvider, sampleSize);
createTest<VariableBlockSizeTest> (parent, plugProvider, sampleSize);
createTest<ProcessFormatTest> (parent, plugProvider, sampleSize);
createTest<BypassPersistenceTest> (parent, plugProvider, sampleSize);
if (extensive)
{
constexpr std::array<SpeakerArrangement, 15> saArray = {
{SpeakerArr::kMono, SpeakerArr::kStereo, SpeakerArr::kStereoSurround,
SpeakerArr::kStereoCenter, SpeakerArr::kStereoSide, SpeakerArr::kStereoCLfe,
SpeakerArr::k30Cine, SpeakerArr::k30Music, SpeakerArr::k31Cine, SpeakerArr::k31Music,
SpeakerArr::k40Cine, SpeakerArr::k40Music, SpeakerArr::k41Cine, SpeakerArr::k41Music,
SpeakerArr::k50}};
for (auto inArr : saArray)
{
for (auto outArr : saArray)
{
createSpeakerArrangementTest (parent, plugProvider, sampleSize, inArr, outArr);
}
}
constexpr std::array<int32, 3> autoRates = {{100, 50, 1}};
constexpr std::array<int32, 4> numParams = {{1, 2, 10, -1}};
for (auto inp : numParams)
{
for (auto iar : autoRates)
{
createTest<AutomationTest> (parent, plugProvider, sampleSize, iar, inp, false);
createTest<AutomationTest> (parent, plugProvider, sampleSize, iar, inp, true);
}
}
}
else
{
createSpeakerArrangementTest (parent, plugProvider, sampleSize, SpeakerArr::kStereo,
SpeakerArr::kStereo);
createSpeakerArrangementTest (parent, plugProvider, sampleSize, SpeakerArr::kMono,
SpeakerArr::kMono);
createTest<AutomationTest> (parent, plugProvider, sampleSize, 100, 1, false);
createTest<AutomationTest> (parent, plugProvider, sampleSize, 100, 1, true);
}
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
IPtr<TestSuite> Validator::createTests (ITestPlugProvider* plugProvider,
const ConstString& plugName, bool extensive)
{
IPtr<TestSuite> plugTestSuite = owned (new TestSuite (plugName));
IPtr<TestSuite> generalTests = owned (new TestSuite ("General Tests"));
// todo: add tests here!
createTest<EditorClassesTest> (generalTests, plugProvider);
createTest<ScanBussesTest> (generalTests, plugProvider);
createTest<ScanParametersTest> (generalTests, plugProvider);
createTest<MidiMappingTest> (generalTests, plugProvider);
createTest<MidiLearnTest> (generalTests, plugProvider);
createTest<UnitInfoTest> (generalTests, plugProvider);
createTest<ProgramInfoTest> (generalTests, plugProvider);
createTest<TerminateInitializeTest> (generalTests, plugProvider);
createTest<UnitStructureTest> (generalTests, plugProvider);
createTest<ValidStateTransitionTest> (generalTests, plugProvider, kSample32);
createTest<ValidStateTransitionTest> (generalTests, plugProvider, kSample64);
// createTest<InvalidStateTransitionTest> (generalTests, plugProvider);
// createTest<RepeatIdenticalStateTransitionTest> (generalTests, plugProvider);
createTest<BusConsistencyTest> (generalTests, plugProvider);
// createTest<BusInvalidIndexTest> (generalTests, plugProvider);
createTest<BusActivationTest> (generalTests, plugProvider);
createTest<CheckAudioBusArrangementTest> (generalTests, plugProvider);
createTest<SideChainArrangementTest> (generalTests, plugProvider);
createTest<SuspendResumeTest> (generalTests, plugProvider, kSample32);
createTest<NoteExpressionTest> (generalTests, plugProvider);
createTest<KeyswitchTest> (generalTests, plugProvider);
createTest<ProcessContextRequirementsTest> (generalTests, plugProvider);
plugTestSuite->addTestSuite (generalTests->getName ().data (), generalTests);
IPtr<TestSuite> singlePrecisionTests =
owned (new TestSuite ("Single Precision (32 bit) Tests"));
createPrecisionTests (singlePrecisionTests, plugProvider, kSample32, extensive);
plugTestSuite->addTestSuite (singlePrecisionTests->getName ().data (), singlePrecisionTests);
IPtr<TestSuite> doublePrecisionTests =
owned (new TestSuite ("Double Precision (64 bit) Tests"));
createPrecisionTests (doublePrecisionTests, plugProvider, kSample64, extensive);
plugTestSuite->addTestSuite (doublePrecisionTests->getName ().data (), doublePrecisionTests);
return plugTestSuite;
}
//------------------------------------------------------------------------
void Validator::addTest (ITestSuite* _testSuite, TestBase* testItem)
{
_testSuite->addTest (testItem->getName (), testItem);
testItem->release ();
}
//------------------------------------------------------------------------
void Validator::runTestSuite (TestSuite* suite, FIDString nameFilter)
{
std::string name;
if (nameFilter == nullptr || suite->getName () == nameFilter)
{
nameFilter = nullptr; // make sure if suiteName is the namefilter that sub suite will run
ITest* testItem = nullptr;
// first run all tests in the suite
for (int32 i = 0; i < suite->getTestCount (); i++)
{
if (suite->getTest (i, testItem, name) == kResultTrue)
{
if (infoStream)
{
*infoStream << "[" << name;
if (auto desc = testItem->getDescription ())
{
auto descStr = StringConvert::convert (desc);
if (!descStr.empty ())
*infoStream << ": " << descStr;
}
*infoStream << "]\n";
}
if (testItem->setup ())
{
bool success = testItem->run (this);
if (success)
{
if (infoStream)
*infoStream << "[Succeeded]\n";
numTestsPassed++;
}
else
{
if (infoStream)
*infoStream << "[XXXXXXX Failed]\n";
if (errorStream && errorStream != infoStream)
{
*errorStream << "Test [" << name << "] ";
*errorStream << "Failed\n";
}
numTestsFailed++;
}
if (!testItem->teardown ())
{
if (infoStream)
*infoStream << "Failed to teardown test!\n";
if (errorStream && errorStream != infoStream)
{
*errorStream << "[" << name << "] ";
*errorStream << "Failed to teardown test!\n";
}
}
}
else
{
testItem->teardown ();
if (infoStream)
*infoStream << "Failed to setup test!\n";
if (errorStream && errorStream != infoStream)
{
*errorStream << "[" << name << "] ";
*errorStream << "Failed to setup test!\n";
}
}
if (infoStream)
*infoStream << "\n";
}
}
}
// next run sub suites
int32 subTestSuiteIndex = 0;
ITestSuite* subSuite = nullptr;
while (suite->getTestSuite (subTestSuiteIndex++, subSuite, name) == kResultTrue)
{
auto* ts = FCast<TestSuite> (subSuite);
if (ts)
{
if (infoStream)
{
*infoStream << SEPARATOR;
*infoStream << "TestSuite : " << ts->getName ().data () << "\n";
*infoStream << SEPARATOR << "\n";
}
runTestSuite (ts, nameFilter);
}
}
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,111 @@
//-----------------------------------------------------------------------------
// Flags : clang-format SMTGSequencer
// Project : VST SDK
//
// Category : Validator
// Filename : public.sdk/samples/vst-hosting/validator/source/validator.h
// Created by : Steinberg, 04/2005
// Description : VST 3 Plug-in Validator class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/pluginterfacesupport.h"
#include "public.sdk/source/vst/testsuite/vsttestsuite.h"
#include "base/source/fstring.h"
#include "pluginterfaces/base/ipluginbase.h"
#include "pluginterfaces/test/itest.h"
#include <iosfwd>
namespace Steinberg {
namespace Vst {
class IComponent;
class IEditController;
class VstModule;
class TestBase;
class TestSuite;
//------------------------------------------------------------------------
/** Main Class of Validator.
* \defgroup Validator VST3 Validator
*/
class Validator : public FObject, public ITestResult, public IHostApplication
{
public:
//------------------------------------------------------------------------
Validator (int argc, char* argv[]);
~Validator () override;
int run ();
//------------------------------------------------------------------------
OBJ_METHODS (Validator, FObject)
REFCOUNT_METHODS (FObject)
tresult PLUGIN_API queryInterface (const char* _iid, void** obj) override;
//------------------------------------------------------------------------
protected:
using Module = VST3::Hosting::Module;
// ITestResult
void PLUGIN_API addErrorMessage (const tchar* msg) override;
void PLUGIN_API addMessage (const tchar* msg) override;
// IHostApplication
tresult PLUGIN_API getName (String128 name) override;
tresult PLUGIN_API createInstance (TUID cid, TUID iid, void** obj) override;
IPtr<TestSuite> createTests (ITestPlugProvider* plugProvider, const ConstString& plugName,
bool extensive);
void addTest (ITestSuite* testSuite, TestBase* test);
void runTestSuite (TestSuite* suite, FIDString nameFilter = nullptr);
struct ModuleTestConfig
{
ModuleTestConfig (bool useGlobalInstance, bool useExtensiveTests,
std::string& customTestComponentPath, std::string& testSuiteName,
VST3::Optional<VST3::UID>&& testProcessor)
: useGlobalInstance (useGlobalInstance)
, useExtensiveTests (useExtensiveTests)
, customTestComponentPath (customTestComponentPath)
, testSuiteName (testSuiteName)
, testProcessor (std::move (testProcessor))
{
}
bool useGlobalInstance {true};
bool useExtensiveTests {false};
std::string customTestComponentPath;
std::string testSuiteName;
VST3::Optional<VST3::UID> testProcessor;
};
void testModule (Module::Ptr module, const ModuleTestConfig& config);
int argc;
char** argv;
IPtr<PlugInterfaceSupport> mPlugInterfaceSupport;
int32 numTestsFailed {0};
int32 numTestsPassed {0};
bool addErrorWarningTextToOutput {true};
std::ostream* infoStream {nullptr};
std::ostream* errorStream {nullptr};
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,14 @@
if(ANDROID)
return()
endif(ANDROID)
# Add VST3 Hosting Samples
if(NOT DEFINED SMTG_ADD_VST3_UTILITIES)
option(SMTG_ADD_VST3_UTILITIES "Build VST3 Utilities" ON)
endif(NOT DEFINED SMTG_ADD_VST3_UTILITIES)
if(SMTG_ADD_VST3_UTILITIES)
include(SMTG_AddSubDirectories)
smtg_add_subdirectories()
endif(SMTG_ADD_VST3_UTILITIES)
@@ -0,0 +1,64 @@
set(moduleinfotool_sources
source/main.cpp
../../../source/vst/moduleinfo/json.h
../../../source/vst/moduleinfo/jsoncxx.h
../../../source/vst/moduleinfo/moduleinfo.h
../../../source/vst/moduleinfo/moduleinfocreator.cpp
../../../source/vst/moduleinfo/moduleinfocreator.h
../../../source/vst/moduleinfo/moduleinfoparser.cpp
../../../source/vst/moduleinfo/moduleinfoparser.h
)
if(SMTG_MAC)
set(moduleinfotool_sources
${moduleinfotool_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm
)
set_source_files_properties(
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
elseif(SMTG_LINUX)
set(moduleinfotool_sources
${moduleinfotool_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp
)
elseif(SMTG_WIN)
set(moduleinfotool_sources
${moduleinfotool_sources}
${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp
)
endif(SMTG_MAC)
set(target moduleinfotool)
add_executable(${target} ${moduleinfotool_sources})
if(SDK_IDE_UTILITIES_FOLDER)
set_target_properties(${target}
PROPERTIES
${SDK_IDE_UTILITIES_FOLDER}
)
endif(SDK_IDE_UTILITIES_FOLDER)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
)
smtg_target_codesign(${target})
smtg_target_setup_universal_binary(${target})
if(APPLE AND NOT XCODE)
find_library(COCOA_FRAMEWORK Cocoa)
target_link_libraries(${target}
PRIVATE
${COCOA_FRAMEWORK}
)
endif(APPLE AND NOT XCODE)
if(MINGW)
set_target_properties(${target} PROPERTIES LINK_FLAGS "-municode")
endif(MINGW)
@@ -0,0 +1,386 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfotool
// Filename : public.sdk/samples/vst-utilities/moduleinfotool/main.cpp
// Created by : Steinberg, 12/2021
// Description : main entry point
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/common/readfile.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/moduleinfo/moduleinfocreator.h"
#include "public.sdk/source/vst/moduleinfo/moduleinfoparser.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "base/source/fcommandline.h"
#include "pluginterfaces/base/fplatform.h"
#include "pluginterfaces/vst/vsttypes.h"
#include <cstdio>
#include <fstream>
#include <iostream>
//------------------------------------------------------------------------
namespace Steinberg {
namespace ModuleInfoTool {
namespace {
//------------------------------------------------------------------------
constexpr auto BUILD_INFO = "moduleinfotool 1.0.0 [Built " __DATE__ "]";
//------------------------------------------------------------------------
//-- Options
constexpr auto optHelp = "help";
constexpr auto optCreate = "create";
constexpr auto optValidate = "validate";
constexpr auto optModuleVersion = "version";
constexpr auto optModulePath = "path";
constexpr auto optInfoPath = "infopath";
constexpr auto optModuleCompatPath = "compat";
constexpr auto optOutputPath = "output";
//------------------------------------------------------------------------
void printUsage (std::ostream& s)
{
s << "Usage:\n";
s << " moduleinfotool -create -version VERSION -path MODULE_PATH [-compat PATH -output PATH]\n";
s << " moduleinfotool -validate -path MODULE_PATH [-infopath PATH]\n";
}
//------------------------------------------------------------------------
std::optional<ModuleInfo::CompatibilityList> openAndParseCompatJSON (const std::string& path)
{
auto data = readFile (path);
if (data.empty ())
{
std::cout << "Can not read '" << path << "'\n";
printUsage (std::cout);
return {};
}
std::stringstream error;
auto result = ModuleInfoLib::parseCompatibilityJson (data, &error);
if (!result)
{
std::cout << "Can not parse '" << path << "'\n";
std::cout << error.str ();
printUsage (std::cout);
return {};
}
return result;
}
//------------------------------------------------------------------------
int createJSON (const std::optional<ModuleInfo::CompatibilityList>& compat,
const std::string& modulePath, const std::string& moduleVersion,
std::ostream& outStream)
{
std::string errorStr;
auto module = VST3::Hosting::Module::create (modulePath, errorStr);
if (!module)
{
std::cout << errorStr;
return 1;
}
auto moduleInfo = ModuleInfoLib::createModuleInfo (*module, false);
if (compat)
moduleInfo.compatibility = *compat;
moduleInfo.version = moduleVersion;
std::stringstream output;
ModuleInfoLib::outputJson (moduleInfo, output);
auto str = output.str ();
outStream << str;
return 0;
}
//------------------------------------------------------------------------
struct validate_error : std::exception
{
validate_error (const std::string& str) : str (str) {}
const char* what () const noexcept override { return str.data (); }
private:
std::string str;
};
//------------------------------------------------------------------------
void validate (const ModuleInfo& moduleInfo, const VST3::Hosting::Module& module)
{
auto factory = module.getFactory ();
auto factoryInfo = factory.info ();
auto classInfoList = factory.classInfos ();
auto snapshotList = module.getSnapshots (module.getPath ());
if (factoryInfo.vendor () != moduleInfo.factoryInfo.vendor)
throw validate_error ("factoryInfo.vendor different: " + moduleInfo.factoryInfo.vendor);
if (factoryInfo.url () != moduleInfo.factoryInfo.url)
throw validate_error ("factoryInfo.url different: " + moduleInfo.factoryInfo.url);
if (factoryInfo.email () != moduleInfo.factoryInfo.email)
throw validate_error ("factoryInfo.email different: " + moduleInfo.factoryInfo.email);
if (factoryInfo.flags () != moduleInfo.factoryInfo.flags)
throw validate_error ("factoryInfo.flags different: " +
std::to_string (moduleInfo.factoryInfo.flags));
for (const auto& ci : moduleInfo.classes)
{
auto cid = VST3::UID::fromString (ci.cid);
if (!cid)
throw validate_error ("could not parse class UID: " + ci.cid);
auto it = std::find_if (classInfoList.begin (), classInfoList.end (),
[&] (const auto& el) { return el.ID () == *cid; });
if (it == classInfoList.end ())
throw validate_error ("cannot find CID in class list: " + ci.cid);
if (it->name () != ci.name)
throw validate_error ("class name different: " + ci.name);
if (it->category () != ci.category)
throw validate_error ("class category different: " + ci.category);
if (it->vendor () != ci.vendor)
throw validate_error ("class vendor different: " + ci.vendor);
if (it->version () != ci.version)
throw validate_error ("class version different: " + ci.version);
if (it->sdkVersion () != ci.sdkVersion)
throw validate_error ("class sdkVersion different: " + ci.sdkVersion);
if (it->subCategories () != ci.subCategories)
throw validate_error ("class subCategories different: " /* + ci.subCategories*/);
if (it->cardinality () != ci.cardinality)
throw validate_error ("class cardinality different: " +
std::to_string (ci.cardinality));
if (it->classFlags () != ci.flags)
throw validate_error ("class flags different: " + std::to_string (ci.flags));
classInfoList.erase (it);
auto snapshotListIt = std::find_if (snapshotList.begin (), snapshotList.end (),
[&] (const auto& el) { return el.uid == *cid; });
if (snapshotListIt == snapshotList.end () && !ci.snapshots.empty ())
throw validate_error ("cannot find snapshots for: " + ci.cid);
for (const auto& snapshot : ci.snapshots)
{
auto snapshotIt = std::find_if (
snapshotListIt->images.begin (), snapshotListIt->images.end (),
[&] (const auto& el) { return el.scaleFactor == snapshot.scaleFactor; });
if (snapshotIt == snapshotListIt->images.end ())
throw validate_error ("cannot find snapshots for scale factor: " +
std::to_string (snapshot.scaleFactor));
std::string_view path (snapshotIt->path);
if (path.find (module.getPath ()) == 0)
path.remove_prefix (module.getPath ().size () + 1);
if (path != snapshot.path)
throw validate_error ("cannot find snapshots with path: " + snapshot.path);
snapshotListIt->images.erase (snapshotIt);
}
if (snapshotListIt != snapshotList.end () && !snapshotListIt->images.empty ())
{
std::string errorStr = "Missing Snapshots in moduleinfo:\n";
for (const auto& s : snapshotListIt->images)
{
errorStr += s.path + '\n';
}
throw validate_error (errorStr);
}
if (snapshotListIt != snapshotList.end ())
snapshotList.erase (snapshotListIt);
}
if (!classInfoList.empty ())
throw validate_error ("Missing classes in moduleinfo");
if (!snapshotList.empty ())
throw validate_error ("Missing snapshots in moduleinfo");
}
//------------------------------------------------------------------------
int validate (const std::string& modulePath, std::string infoJsonPath)
{
if (infoJsonPath.empty ())
{
auto path = VST3::Hosting::Module::getModuleInfoPath (modulePath);
if (!path)
{
std::cerr << "Module does not contain a moduleinfo.json: '" << modulePath << "'"
<< '\n';
return 1;
}
infoJsonPath = *path;
}
auto data = readFile (infoJsonPath);
if (data.empty ())
{
std::cerr << "Empty or non existing file: '" << infoJsonPath << "'" << '\n';
printUsage (std::cout);
return 1;
}
auto moduleInfo = ModuleInfoLib::parseJson (data, &std::cerr);
if (moduleInfo)
{
std::string errorStr;
auto module = VST3::Hosting::Module::create (modulePath, errorStr);
if (!module)
{
std::cerr << errorStr;
printUsage (std::cout);
return 1;
}
try
{
validate (*moduleInfo, *module);
}
catch (const std::exception& exc)
{
std::cerr << "Error:\n" << exc.what () << '\n';
printUsage (std::cout);
return 1;
}
return 0;
}
printUsage (std::cout);
return 1;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
int run (int argc, char* argv[])
{
// parse command line
CommandLine::Descriptions desc;
CommandLine::VariablesMap valueMap;
CommandLine::FilesVector files;
using Description = CommandLine::Description;
desc.addOptions (
BUILD_INFO,
{
{optCreate, "Create moduleinfo", Description::kBool},
{optValidate, "Validate moduleinfo", Description::kBool},
{optModuleVersion, "Module version", Description::kString},
{optModulePath, "Path to module", Description::kString},
{optInfoPath, "Path to moduleinfo.json", Description::kString},
{optModuleCompatPath, "Path to compatibility.json", Description::kString},
{optOutputPath, "Write json to file instead of stdout", Description::kString},
{optHelp, "Print help", Description::kBool},
});
CommandLine::parse (argc, argv, desc, valueMap, &files);
bool isCreate = valueMap.count (optCreate) != 0 && valueMap.count (optModuleVersion) != 0 &&
valueMap.count (optModulePath) != 0;
bool isValidate = valueMap.count (optValidate) && valueMap.count (optModulePath) != 0;
if (valueMap.hasError () || valueMap.count (optHelp) || !(isCreate || isValidate))
{
std::cout << '\n' << desc << '\n';
printUsage (std::cout);
return 1;
}
int result = 1;
const auto& modulePath = valueMap[optModulePath];
if (isCreate)
{
auto* outputStream = &std::cout;
std::optional<ModuleInfo::CompatibilityList> compat;
if (valueMap.count (optModuleCompatPath) != 0)
{
const auto& compatPath = valueMap[optModuleCompatPath];
compat = openAndParseCompatJSON (compatPath);
if (!compat)
return 1;
}
bool writeToFile = false;
if (valueMap.count (optOutputPath) != 0)
{
writeToFile = true;
#if SMTG_OS_WINDOWS
auto tmp = Vst::StringConvert::convert (valueMap[optOutputPath]);
auto outputFile = reinterpret_cast<const wchar_t*> (tmp.data ());
#else
auto outputFile = valueMap[optOutputPath];
#endif
auto ostream = new std::ofstream (outputFile);
if (ostream->is_open ())
outputStream = ostream;
else
{
std::cout << "Cannot create output file: " << valueMap[optOutputPath] << '\n';
return result;
}
}
const auto& moduleVersion = valueMap[optModuleVersion];
result = createJSON (compat, modulePath, moduleVersion, *outputStream);
if (writeToFile)
delete outputStream;
}
else if (isValidate)
{
std::string moduleInfoJsonPath;
if (valueMap.count (optInfoPath) != 0)
moduleInfoJsonPath = valueMap[optInfoPath];
result = validate (modulePath, moduleInfoJsonPath);
}
return result;
}
//------------------------------------------------------------------------
} // ModuleInfoTool
} // Steinberg
//------------------------------------------------------------------------
#if SMTG_OS_WINDOWS
//------------------------------------------------------------------------
using Utf8String = std::string;
//------------------------------------------------------------------------
using Utf8Args = std::vector<Utf8String>;
Utf8Args toUtf8Args (int argc, wchar_t* wargv[])
{
Utf8Args utf8Args;
for (int i = 0; i < argc; i++)
{
auto str = reinterpret_cast<const Steinberg::Vst::TChar*> (wargv[i]);
utf8Args.push_back (Steinberg::Vst::StringConvert::convert (str));
}
return utf8Args;
}
//------------------------------------------------------------------------
using Utf8ArgPtrs = std::vector<char*>;
Utf8ArgPtrs toUtf8ArgPtrs (Utf8Args& utf8Args)
{
Utf8ArgPtrs utf8ArgPtrs;
for (auto& el : utf8Args)
{
utf8ArgPtrs.push_back (el.data ());
}
return utf8ArgPtrs;
}
//------------------------------------------------------------------------
int wmain (int argc, wchar_t* wargv[])
{
Utf8Args utf8Args = toUtf8Args (argc, wargv);
Utf8ArgPtrs utf8ArgPtrs = toUtf8ArgPtrs (utf8Args);
char** argv = &(utf8ArgPtrs.at (0));
return Steinberg::ModuleInfoTool::run (argc, argv);
}
#else
//------------------------------------------------------------------------
int main (int argc, char* argv[])
{
return Steinberg::ModuleInfoTool::run (argc, argv);
}
//------------------------------------------------------------------------
#endif // SMTG_OS_WINDOWS
@@ -0,0 +1,67 @@
function(smtg_target_setup_options target)
set(options)
set(oneValueArgs
BUNDLE_IDENTIFIER
COMPANY_NAME
)
set(multiValueArgs)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if(ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: The following parameters are unrecognized: ${ARG_UNPARSED_ARGUMENTS}")
endif()
if(NOT ARG_BUNDLE_IDENTIFIER)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: BUNDLE_IDENTIFIER must be specified")
endif()
if(NOT ARG_COMPANY_NAME)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: ARG_COMPANY_NAME must be specified")
endif()
smtg_target_configure_version_file(${target})
if(SMTG_MAC)
smtg_target_set_bundle(${target}
BUNDLE_IDENTIFIER "${ARG_BUNDLE_IDENTIFIER}"
COMPANY_NAME "${ARG_COMPANY_NAME}"
)
elseif(SMTG_WIN)
target_sources(${target}
PRIVATE
resource/info.rc
)
endif()
target_link_libraries(${target}
PRIVATE
sdk
)
endfunction()
function(smtg_target_setup_as_vst3_example target)
set_target_properties(${target}
PROPERTIES
${SDK_IDE_PLUGIN_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
smtg_target_setup_options(${target}
BUNDLE_IDENTIFIER "com.steinberg.vst3.${target}"
COMPANY_NAME "Steinberg Media Technologies"
)
endfunction()
if(ANDROID)
add_subdirectory(adelay)
return()
endif(ANDROID)
include(SMTG_AddSubDirectories)
smtg_add_subdirectories()
@@ -0,0 +1,2 @@
/DerivedData
/build
@@ -0,0 +1,102 @@
# iOS target
if(SMTG_MAC AND SMTG_BUILD_INTERAPPAUDIO)
if(XCODE AND SMTG_ENABLE_IOS_TARGETS)
set(target noteexpressionsynth_iaa_ios)
set(${target}_xib_resources
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/InterAppAudio/Images.xcassets
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/InterAppAudio/LaunchScreen.storyboard
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/InterAppAudio/noteexpressionsynth_ios.entitlements
${SDK_ROOT}/public.sdk/source/vst/interappaudio/PresetBrowserView.xib
${SDK_ROOT}/public.sdk/source/vst/interappaudio/PresetSaveView.xib
${SDK_ROOT}/public.sdk/source/vst/interappaudio/SettingsView.xib
InterAppAudioExample/VSTInterAppAudioHostUIControllerView.xib
)
set(${target}_sources
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/brownnoise.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/factory.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/filter.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_controller.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_controller.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_processor.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_processor.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_ui.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_ui.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_voice.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_expression_synth_voice.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_touch_controller.cpp
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/note_touch_controller.h
${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/source/version.h
${SDK_ROOT}/public.sdk/source/main/macmain.cpp
${SDK_ROOT}/public.sdk/source/vst/auwrapper/NSDataIBStream.mm
${SDK_ROOT}/public.sdk/source/vst/hosting/eventlist.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/hostclasses.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/parameterchanges.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/processdata.cpp
${SDK_ROOT}/public.sdk/source/vst/vstguieditor.cpp
${VSTGUI_ROOT}/vstgui4/vstgui/contrib/keyboardview.cpp
${VSTGUI_ROOT}/vstgui4/vstgui/contrib/keyboardview.h
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3editor.cpp
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3editor.h
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3groupcontroller.cpp
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3groupcontroller.h
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3padcontroller.cpp
${VSTGUI_ROOT}/vstgui4/vstgui/plugin-bindings/vst3padcontroller.h
${VSTGUI_ROOT}/vstgui4/vstgui/vstgui_ios.mm
${VSTGUI_ROOT}/vstgui4/vstgui/vstgui_uidescription.cpp
InterAppAudioExample/VSTInterAppAudioAppDelegate.mm
InterAppAudioExample/VSTInterAppAudioHostUIControllerViewController.mm
InterAppAudioExample/main.m
)
set(prefix_header ${SDK_ROOT}/public.sdk/samples/vst/InterAppAudio/InterAppAudioExample/InterAppAudioExample-Prefix.pch)
add_executable(${target} ${${target}_sources} ${${target}_xib_resources})
smtg_target_set_platform_ios(${target})
target_compile_options(${target}
PUBLIC
-DVSTGUI_LIVE_EDITING=0
) # NO VSTGUI LIVE EDITING SUPPORT FOR iOS
set_target_properties(${target}
PROPERTIES
${SDK_IDE_PLUGIN_EXAMPLES_FOLDER}
)
set_target_properties(${target}
PROPERTIES
BUNDLE TRUE
RESOURCE "${${target}_xib_resources}"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_LIST_DIR}/../note_expression_synth/resource/InterAppAudio/noteexpressionsynth_ios.entitlements"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${SMTG_CODE_SIGN_IDENTITY_IOS}"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" # prevent warning
XCODE_ATTRIBUTE_GCC_PREFIX_HEADER ${prefix_header}
XCODE_ATTRIBUTE_GENERATE_PKGINFO_FILE "YES"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${SMTG_IOS_DEVELOPMENT_TEAM}
)
target_link_libraries(${target}
PRIVATE
interappaudio
"-framework QuartzCore"
"-framework MobileCoreServices"
"-framework Accelerate"
"-framework ImageIO"
"-framework GLKit"
"-framework CoreText"
)
smtg_target_set_bundle(${target} INFOPLIST "${CMAKE_CURRENT_LIST_DIR}/../note_expression_synth/resource/InterAppAudio/NoteExpressionSynthExample.plist" PREPROCESS)
target_include_directories(${target}
PUBLIC
${ROOT}/vstgui4
)
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/about.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/background.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/groupframe.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/knob big.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/knob.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/knob2.png")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/note_expression_synth.uidesc")
smtg_target_add_vst3_resource(${target} "${SDK_ROOT}/public.sdk/samples/vst/note_expression_synth/resource/vst3_logo_small.png")
else()
message("[SMTG] * To enable building the InterAppAudio NoteExpressionSynth example for iOS you need to set the SMTG_IOS_DEVELOPMENT_TEAM and use the Xcode generator")
endif(XCODE AND SMTG_ENABLE_IOS_TARGETS)
endif(SMTG_MAC AND SMTG_BUILD_INTERAPPAUDIO)
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AudioComponents</key>
<array>
<dict>
<key>manufacturer</key>
<string>SMTG</string>
<key>name</key>
<string>ADelay VST3</string>
<key>subtype</key>
<string>adly</string>
<key>type</key>
<string>aurx</string>
<key>version</key>
<integer>1</integer>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.interappaudioexamples.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIStatusBarHidden~ipad</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
@@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/InterAppAudioExample-Prefix.pch
// Created by : Steinberg, 08/2013
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#endif
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>inter-app-audio</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,26 @@
// clang-format off
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/VSTInterAppAudioAppDelegate.h
// Created by : Steinberg, 08/2013
// Description :
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
// clang-format on
#import "public.sdk/source/vst/interappaudio/VSTInterAppAudioAppDelegateBase.h"
//------------------------------------------------------------------------
@interface VSTInterAppAudioAppDelegate : VSTInterAppAudioAppDelegateBase
//------------------------------------------------------------------------
@end
@@ -0,0 +1,119 @@
// clang-format off
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/VSTInterAppAudioAppDelegate.mm
// Created by : Steinberg, 08/2013
// Description :
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
// clang-format on
#import "VSTInterAppAudioAppDelegate.h"
#import "VSTInterAppAudioHostUIControllerViewController.h"
#import "public.sdk/source/vst/interappaudio/AudioIO.h"
using namespace Steinberg::Vst::InterAppAudio;
//------------------------------------------------------------------------
@interface VSTInterAppAudioAppDelegate ()
//------------------------------------------------------------------------
{
UIButton* showHostView;
}
@end
//------------------------------------------------------------------------
@implementation VSTInterAppAudioAppDelegate
//------------------------------------------------------------------------
//------------------------------------------------------------------------
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
if ([super application:application didFinishLaunchingWithOptions:launchOptions])
{
[self performSelector:@selector (createShowHostViewButton) withObject:nil afterDelay:0.2];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector (audioIOConnectionChanged)
name:AudioIO::kConnectionStateChange
object:nil];
return YES;
}
return NO;
}
//------------------------------------------------------------------------
- (NSUInteger)application:(UIApplication*)application
supportedInterfaceOrientationsForWindow:(UIWindow*)window
{
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}
//------------------------------------------------------------------------
- (void)audioIOConnectionChanged
{
showHostView.hidden = AudioIO::instance ()->getInterAppAudioConnected () == false;
if (showHostView.hidden)
{
for (id childController in [self.window.rootViewController childViewControllers])
{
if ([childController
isKindOfClass:[VSTInterAppAudioHostUIControllerViewController class]])
{
[childController hideView:self];
}
}
}
}
//------------------------------------------------------------------------
- (void)createShowHostViewButton
{
showHostView = [UIButton buttonWithType:UIButtonTypeInfoDark];
[showHostView addTarget:self
action:@selector (showHostViewAction:)
forControlEvents:UIControlEventTouchDown];
const CGFloat margin = 15;
CGRect frame = showHostView.frame;
frame.origin.y =
[self.window.rootViewController.view bounds].size.height - (frame.size.height + margin);
frame.origin.x = margin;
showHostView.frame = frame;
[self.window.rootViewController.view addSubview:showHostView];
if (AudioIO::instance ()->getInterAppAudioConnected () == false)
{
showHostView.hidden = YES;
}
}
//------------------------------------------------------------------------
- (void)showHostViewAction:(id)sender
{
VSTInterAppAudioHostUIControllerViewController* controller =
[[VSTInterAppAudioHostUIControllerViewController alloc] init];
[self.window.rootViewController addChildViewController:controller];
CGRect frame = controller.view.frame;
frame.origin.y = [self.window.rootViewController.view bounds].size.height;
controller.view.frame = frame;
[self.window.rootViewController.view addSubview:controller.view];
frame.origin.y = [self.window.rootViewController.view bounds].size.height - frame.size.height;
[UIView animateWithDuration:0.3 animations:^{ controller.view.frame = frame; }];
}
@end
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="4471.1" systemVersion="12E55" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3697.3"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="VSTInterAppAudioHostUIControllerViewController">
<connections>
<outlet property="hostButton" destination="vf0-Cd-0Xg" id="wcF-LJ-CxA"/>
<outlet property="view" destination="2" id="3"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
<rect key="frame" x="0.0" y="0.0" width="492" height="104"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vf0-Cd-0Xg">
<rect key="frame" x="408" y="20" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
<state key="normal" title="Goto Host">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="switchToHost:" destination="-1" eventType="touchUpInside" id="ttD-Qy-eUd"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s4k-Xf-tiu">
<rect key="frame" x="0.0" y="60" width="73" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" title="Close">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="hideView:" destination="-1" eventType="touchUpInside" id="MDK-lh-cXb"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b7T-FM-ORL">
<rect key="frame" x="286" y="20" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="37"/>
<state key="normal" title="▶">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="play:" destination="-1" eventType="touchUpInside" id="sTf-N9-aIV"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iQU-IE-WGe">
<rect key="frame" x="214" y="20" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="37"/>
<state key="normal" title="⏪">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="rewind:" destination="-1" eventType="touchUpInside" id="wCj-ym-Zbp"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oIG-ts-Ybg">
<rect key="frame" x="142" y="20" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="37"/>
<state key="normal" title="🔴">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="record:" destination="-1" eventType="touchUpInside" id="FDc-fT-H5V"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</view>
</objects>
</document>
@@ -0,0 +1,34 @@
// clang-format off
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/VSTInterAppAudioHostUIControllerViewController.h
// Created by : Steinberg, 08/2013
// Description :
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
// clang-format on
#import <UIKit/UIKit.h>
//------------------------------------------------------------------------
@interface VSTInterAppAudioHostUIControllerViewController : UIViewController
//------------------------------------------------------------------------
- (IBAction)hideView:(id)sender;
- (IBAction)switchToHost:(id)sender;
- (IBAction)play:(id)sender;
- (IBAction)rewind:(id)sender;
- (IBAction)record:(id)sender;
@end
@@ -0,0 +1,116 @@
// clang-format off
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/VSTInterAppAudioHostUIControllerViewController.mm
// Created by : Steinberg, 08/2013
// Description :
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
// clang-format on
#import "VSTInterAppAudioHostUIControllerViewController.h"
#import "public.sdk/source/vst/interappaudio/AudioIO.h"
using namespace Steinberg::Vst::InterAppAudio;
//------------------------------------------------------------------------
static UIImage* scaleImageToSize (UIImage* image, CGSize newSize)
{
UIGraphicsBeginImageContextWithOptions (newSize, NO, 0.0);
[image drawInRect:CGRectMake (0, 0, newSize.width, newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext ();
UIGraphicsEndImageContext ();
return newImage;
}
//------------------------------------------------------------------------
@interface VSTInterAppAudioHostUIControllerViewController ()
//------------------------------------------------------------------------
@property (assign) IBOutlet UIButton* hostButton;
@end
//------------------------------------------------------------------------
@implementation VSTInterAppAudioHostUIControllerViewController
//------------------------------------------------------------------------
//------------------------------------------------------------------------
- (id)init
{
self = [super initWithNibName:@"VSTInterAppAudioHostUIControllerView" bundle:nil];
if (self)
{
}
return self;
}
//------------------------------------------------------------------------
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage* image = AudioIO::instance ()->getHostIcon ();
if (image)
{
image = scaleImageToSize (image, self.hostButton.bounds.size);
[self.hostButton setTitle:@"" forState:UIControlStateNormal];
[self.hostButton setImage:image forState:UIControlStateNormal];
}
self.view.layer.shadowColor = [[UIColor blackColor] CGColor];
self.view.layer.shadowOpacity = 0.5;
self.view.layer.shadowRadius = 5;
self.view.layer.shadowOffset = CGSizeMake (5, -5);
}
//------------------------------------------------------------------------
- (IBAction)hideView:(id)sender
{
[UIView animateWithDuration:0.3
animations:^{
CGRect frame = self.view.frame;
frame.origin.y += frame.size.height;
self.view.frame = frame;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
}];
}
//------------------------------------------------------------------------
- (IBAction)switchToHost:(id)sender
{
AudioIO::instance ()->switchToHost ();
}
//------------------------------------------------------------------------
- (IBAction)play:(id)sender
{
AudioIO::instance ()->sendRemoteControlEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
}
//------------------------------------------------------------------------
- (IBAction)rewind:(id)sender
{
AudioIO::instance ()->sendRemoteControlEvent (kAudioUnitRemoteControlEvent_Rewind);
}
//------------------------------------------------------------------------
- (IBAction)record:(id)sender
{
AudioIO::instance ()->sendRemoteControlEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
}
@end
@@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */
@@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/interappaudio/InterAppAudioExample/main.m
// Created by : Steinberg, 08/2013
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#import <UIKit/UIKit.h>
#import "VSTInterAppAudioAppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain (argc, argv, nil, NSStringFromClass ([VSTInterAppAudioAppDelegate class]));
}
}
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.25.0)
project(smtg-vst3-adelay
VERSION ${vstsdk_VERSION}.0
DESCRIPTION "Steinberg VST 3 ADelay example"
)
smtg_add_vst3plugin(adelay
source/adelaycontroller.cpp
source/adelaycontroller.h
source/adelayids.h
source/adelayprocessor.cpp
source/adelayprocessor.h
source/exampletest.cpp
source/factory.cpp
source/version.h
${SDK_ROOT}/public.sdk/source/vst/utility/test/ringbuffertest.cpp
${SDK_ROOT}/public.sdk/source/vst/utility/test/versionparsertest.cpp
)
smtg_target_setup_as_vst3_example(adelay)
@@ -0,0 +1,18 @@
# ADelay
## Introduction
**ADelay** is a simple FX plug-in with just one parameter for delay control.
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/Plug-in+Examples.html#adelay).
## Getting Started
This plug-in is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,45 @@
#include <windows.h>
#include "../source/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Version
/////////////////////////////////////////////////////////////////////////////
VS_VERSION_INFO VERSIONINFO
FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040004e4"
BEGIN
VALUE "FileVersion", FULL_VERSION_STR
VALUE "ProductVersion", FULL_VERSION_STR
VALUE "OriginalFilename", stringOriginalFilename
VALUE "FileDescription", stringFileDescription
VALUE "InternalName", stringFileDescription
VALUE "ProductName", stringFileDescription
VALUE "CompanyName", stringCompanyName
VALUE "LegalCopyright", stringLegalCopyright
VALUE "LegalTrademarks", stringLegalTrademarks
//VALUE "PrivateBuild", " \0"
//VALUE "SpecialBuild", " \0"
//VALUE "Comments", " \0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x400, 1252
END
END
@@ -0,0 +1,90 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/adelaycontroller.cpp
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "adelaycontroller.h"
#include "adelayids.h"
#include "pluginterfaces/base/ibstream.h"
#if TARGET_OS_IPHONE
#include "interappaudio/iosEditor.h"
#endif
#include "base/source/fstreamer.h"
namespace Steinberg {
namespace Vst {
DEF_CLASS_IID (IDelayTestController)
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayController::initialize (FUnknown* context)
{
tresult result = EditController::initialize (context);
if (result == kResultTrue)
{
parameters.addParameter (STR16 ("Bypass"), nullptr, 1, 0, ParameterInfo::kCanAutomate|ParameterInfo::kIsBypass, kBypassId);
parameters.addParameter (STR16 ("Delay"), STR16 ("sec"), 0, 1, ParameterInfo::kCanAutomate, kDelayId);
}
return kResultTrue;
}
#if TARGET_OS_IPHONE
//-----------------------------------------------------------------------------
IPlugView* PLUGIN_API ADelayController::createView (FIDString name)
{
if (FIDStringsEqual (name, ViewType::kEditor))
{
return new ADelayEditorForIOS (this);
}
return 0;
}
#endif
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayController::setComponentState (IBStream* state)
{
// we receive the current state of the component (processor part)
// we read only the gain and bypass value...
if (!state)
return kResultFalse;
IBStreamer streamer (state, kLittleEndian);
float savedDelay = 0.f;
if (streamer.readFloat (savedDelay) == false)
return kResultFalse;
setParamNormalized (kDelayId, static_cast<ParamValue> (savedDelay));
int32 bypassState = 0;
if (streamer.readInt32 (bypassState) == false)
{
// could be an old version, continue
}
setParamNormalized (kBypassId, bypassState ? 1 : 0);
return kResultOk;
}
//------------------------------------------------------------------------
bool PLUGIN_API ADelayController::doTest ()
{
// this is called when running thru the validator
// we can now run our own test cases
return true;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/adelaycontroller.h
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/vsteditcontroller.h"
#if SMTG_OS_MACOS
#include <TargetConditionals.h>
#endif
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
class IDelayTestController : public FUnknown
{
public:
virtual bool PLUGIN_API doTest () = 0;
//------------------------------------------------------------------------
static const FUID iid;
};
DECLARE_CLASS_IID (IDelayTestController, 0x9FC98F39, 0x27234512, 0x84FBC4AD, 0x618A14FD)
//-----------------------------------------------------------------------------
class ADelayController : public EditController, public IDelayTestController
{
public:
//------------------------------------------------------------------------
// create function required for plug-in factory,
// it will be called to create new instances of this controller
//------------------------------------------------------------------------
static FUnknown* createInstance (void*) { return (IEditController*)new ADelayController (); }
//---from IPluginBase--------
tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE;
//---from EditController-----
#if TARGET_OS_IPHONE
IPlugView* PLUGIN_API createView (FIDString name) SMTG_OVERRIDE;
#endif
tresult PLUGIN_API setComponentState (IBStream* state) SMTG_OVERRIDE;
bool PLUGIN_API doTest () SMTG_OVERRIDE;
//---Interface---------
OBJ_METHODS (ADelayController, EditController)
DEFINE_INTERFACES
DEF_INTERFACE (IDelayTestController)
END_DEFINE_INTERFACES (EditController)
REFCOUNT_METHODS (EditController)
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,34 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/adelayids.h
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
namespace Steinberg {
namespace Vst {
// parameter tags
enum {
kDelayId = 100,
kBypassId = 101
};
// unique class ids
static DECLARE_UID (ADelayProcessorUID, 0x0CDBB669, 0x85D548A9, 0xBFD83719, 0x09D24BB3);
static DECLARE_UID (ADelayControllerUID, 0x038E7FA9, 0x629A4EAA, 0x8541B889, 0x18E8952C);
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,226 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/adelayprocessor.cpp
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "adelayprocessor.h"
#include "adelayids.h"
#include "base/source/fstreamer.h"
#include "pluginterfaces/base/ibstream.h"
#include "pluginterfaces/vst/ivstparameterchanges.h"
#include <algorithm>
#include <cstdlib>
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
ADelayProcessor::ADelayProcessor ()
{
setControllerClass (FUID::fromTUID (ADelayControllerUID));
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::initialize (FUnknown* context)
{
tresult result = AudioEffect::initialize (context);
if (result == kResultTrue)
{
addAudioInput (STR16 ("AudioInput"), SpeakerArr::kStereo);
addAudioOutput (STR16 ("AudioOutput"), SpeakerArr::kStereo);
mNumChannels = 2;
}
return result;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::setBusArrangements (SpeakerArrangement* inputs, int32 numIns,
SpeakerArrangement* outputs, int32 numOuts)
{
// we only support one in and output bus and these busses must have the same number of channels
if (numIns == 1 && numOuts == 1 && inputs[0] == outputs[0])
{
tresult res = AudioEffect::setBusArrangements (inputs, numIns, outputs, numOuts);
if (res == kResultOk)
mNumChannels = SpeakerArr::getChannelCount (outputs[0]);
return res;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
bool ADelayProcessor::resetDelay ()
{
if (!mBuffer)
return false;
size_t size = static_cast<size_t> (processSetup.sampleRate * sizeof (float) + 0.5);
for (int32 channel = 0; channel < mNumChannels; channel++)
{
if (mBuffer[channel])
memset (mBuffer[channel], 0, size);
}
mBufferPos = 0;
return true;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::setActive (TBool state)
{
if (mBuffer)
{
for (int32 channel = 0; channel < mNumChannels; channel++)
{
std::free (mBuffer[channel]);
}
std::free (mBuffer);
mBuffer = nullptr;
}
if (state)
{
mBuffer = (float**)std::malloc (mNumChannels * sizeof (float*));
if (mBuffer)
{
size_t size = static_cast<size_t> (processSetup.sampleRate * sizeof (float) + 0.5);
for (int32 channel = 0; channel < mNumChannels; channel++)
{
mBuffer[channel] = (float*)std::malloc (size); // 1 second delay max
}
resetDelay ();
}
}
return AudioEffect::setActive (state);
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::setProcessing (TBool state)
{
if (state)
{
resetDelay ();
}
return kResultOk;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::process (ProcessData& data)
{
if (data.inputParameterChanges)
{
int32 numParamsChanged = data.inputParameterChanges->getParameterCount ();
for (int32 index = 0; index < numParamsChanged; index++)
{
if (IParamValueQueue* paramQueue = data.inputParameterChanges->getParameterData (index))
{
ParamValue value;
int32 sampleOffset;
int32 numPoints = paramQueue->getPointCount ();
switch (paramQueue->getParameterId ())
{
case kDelayId:
if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) ==
kResultTrue)
mDelay = value;
break;
case kBypassId:
if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) ==
kResultTrue)
{
mBypass = (value > 0.5f);
}
break;
}
}
}
}
if (data.numSamples > 0)
{
SpeakerArrangement arr;
getBusArrangement (kOutput, 0, arr);
int32 numChannels = SpeakerArr::getChannelCount (arr);
// TODO do something in Bypass : copy input to output if necessary...
// you could use a BypassProcessor which is used in the SyncDelay example
// apply delay
// we have a minimum of 1 sample delay here
int32 delayInSamples = std::max<int32> (1, (int32) (mDelay * processSetup.sampleRate));
for (int32 channel = 0; channel < numChannels; channel++)
{
float* inputChannel = data.inputs[0].channelBuffers32[channel];
float* outputChannel = data.outputs[0].channelBuffers32[channel];
int32 tempBufferPos = mBufferPos;
for (int32 sample = 0; sample < data.numSamples; sample++)
{
float tempSample = inputChannel[sample];
outputChannel[sample] = mBuffer[channel][tempBufferPos];
mBuffer[channel][tempBufferPos] = tempSample;
tempBufferPos++;
if (tempBufferPos >= delayInSamples)
tempBufferPos = 0;
}
}
mBufferPos += data.numSamples;
while (delayInSamples && mBufferPos >= delayInSamples)
mBufferPos -= delayInSamples;
}
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::setState (IBStream* state)
{
if (!state)
return kResultFalse;
// called when we load a preset, the model has to be reloaded
IBStreamer streamer (state, kLittleEndian);
float savedDelay = 0.f;
if (streamer.readFloat (savedDelay) == false)
return kResultFalse;
int32 savedBypass = 0;
if (streamer.readInt32 (savedBypass) == false)
{
// could be an old version, continue
}
mDelay = static_cast<ParamValue> (savedDelay);
mBypass = savedBypass > 0;
return kResultOk;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayProcessor::getState (IBStream* state)
{
// here we need to save the model
IBStreamer streamer (state, kLittleEndian);
streamer.writeFloat (static_cast<float> (mDelay));
streamer.writeInt32 (mBypass ? 1 : 0);
return kResultOk;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/adelayprocessor.cpp
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/vstaudioeffect.h"
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
class ADelayProcessor : public AudioEffect
{
public:
ADelayProcessor ();
tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE;
tresult PLUGIN_API setBusArrangements (SpeakerArrangement* inputs, int32 numIns,
SpeakerArrangement* outputs,
int32 numOuts) SMTG_OVERRIDE;
tresult PLUGIN_API setActive (TBool state) SMTG_OVERRIDE;
tresult PLUGIN_API setProcessing (TBool state) SMTG_OVERRIDE;
tresult PLUGIN_API process (ProcessData& data) SMTG_OVERRIDE;
//------------------------------------------------------------------------
tresult PLUGIN_API setState (IBStream* state) SMTG_OVERRIDE;
tresult PLUGIN_API getState (IBStream* state) SMTG_OVERRIDE;
static FUnknown* createInstance (void*) { return (IAudioProcessor*)new ADelayProcessor (); }
protected:
bool resetDelay ();
ParamValue mDelay {1.};
float** mBuffer {nullptr};
int32 mBufferPos {0};
int32 mNumChannels {0};
bool mBypass {false};
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/exampletest.cpp
// Created by : Steinberg, 10/2010
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "adelaycontroller.h"
#include "adelayprocessor.h"
#include "base/source/fstring.h"
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/testsuite/vsttestsuite.h"
#include "public.sdk/source/vst/utility/testing.h"
#include "pluginterfaces/base/funknownimpl.h"
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
static ModuleInitializer InitTests ([] () {
registerTest ("ExampleTest", nullptr, [] (FUnknown* context, ITestResult* testResult) {
auto plugProvider = U::cast<ITestPlugProvider> (context);
if (plugProvider)
{
auto controller = plugProvider->getController ();
auto testController = U::cast<IDelayTestController> (controller);
if (!controller)
{
testResult->addErrorMessage (String ("Unknown IEditController"));
return false;
}
bool result = testController->doTest ();
plugProvider->releasePlugIn (nullptr, controller);
return (result);
}
return false;
});
});
//------------------------------------------------------------------------
}} // namespaces
@@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/factory.cpp
// Created by : Steinberg, 06/2009
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "adelaycontroller.h"
#include "adelayids.h"
#include "adelayprocessor.h"
#include "version.h" // for versioning
#include "public.sdk/source/main/pluginfactory_constexpr.h"
#include "public.sdk/source/vst/utility/testing.h"
#define stringPluginName "ADelay"
BEGIN_FACTORY_DEF (stringCompanyName, stringCompanyWeb, stringCompanyEmail, 3)
DEF_CLASS (Steinberg::Vst::ADelayProcessorUID, Steinberg::PClassInfo::kManyInstances,
kVstAudioEffectClass,
stringPluginName,
Steinberg::Vst::kDistributable,
"Fx|Delay",
FULL_VERSION_STR, // Plug-in version (to be changed)
kVstVersionString,
Steinberg::Vst::ADelayProcessor::createInstance,
nullptr)
DEF_CLASS (Steinberg::Vst::ADelayControllerUID, Steinberg::PClassInfo::kManyInstances,
kVstComponentControllerClass,
stringPluginName "Controller", // controller name (can be the same as the component name)
0, // not used here
"", // not used here
FULL_VERSION_STR, // Plug-in version (to be changed)
kVstVersionString,
Steinberg::Vst::ADelayController::createInstance,
nullptr)
// add Test Factory
DEF_CLASS (Steinberg::Vst::TestFactoryUID,
Steinberg::PClassInfo::kManyInstances,
kTestClass,
stringPluginName "Test Factory",
0,
"",
"",
"",
Steinberg::Vst::createTestFactoryInstance,
nullptr)
END_FACTORY
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="4488.2" systemVersion="12E55" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3715.3"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ADelayViewController">
<connections>
<outlet property="slider" destination="8qe-I8-OQ4" id="zbQ-xw-Cls"/>
<outlet property="view" destination="1" id="cvV-er-vQe"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="1">
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="8qe-I8-OQ4">
<rect key="frame" x="146" y="370" width="777" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="sliderChanged:" destination="-1" eventType="valueChanged" id="dXh-wG-NCY"/>
</connections>
</slider>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
</view>
</objects>
</document>
@@ -0,0 +1,85 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/interappaudio/iosEditor.h
// Created by : Steinberg, 08/2013
// Description :
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Steinberg Media Technologies nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#ifndef __iosEditor__
#define __iosEditor__
#include "base/source/fobject.h"
#include "pluginterfaces/gui/iplugview.h"
#if __OBJC__
@class ADelayViewController;
#else
struct ADelayViewController;
#endif
namespace Steinberg {
namespace Vst {
class EditController;
class ADelayEditorForIOS : public FObject, public IPlugView
{
public:
ADelayEditorForIOS (EditController* editController);
OBJ_METHODS(ADelayEditorForIOS, FObject)
REFCOUNT_METHODS(FObject)
DEFINE_INTERFACES
DEF_INTERFACE(IPlugView)
END_DEFINE_INTERFACES(FObject)
protected:
tresult PLUGIN_API isPlatformTypeSupported (FIDString type) override;
tresult PLUGIN_API attached (void* parent, FIDString type) override;
tresult PLUGIN_API removed () override;
tresult PLUGIN_API onWheel (float distance) override;
tresult PLUGIN_API onKeyDown (char16 key, int16 keyCode, int16 modifiers) override;
tresult PLUGIN_API onKeyUp (char16 key, int16 keyCode, int16 modifiers) override;
tresult PLUGIN_API getSize (ViewRect* size) override;
tresult PLUGIN_API onSize (ViewRect* newSize) override;
tresult PLUGIN_API onFocus (TBool state) override;
tresult PLUGIN_API setFrame (IPlugFrame* frame) override;
tresult PLUGIN_API canResize () override;
tresult PLUGIN_API checkSizeConstraint (ViewRect* rect) override;
void PLUGIN_API update (FUnknown* changedUnknown, int32 message) override;
EditController* editController;
ADelayViewController* viewController;
};
}}
#endif // __iosEditor__
@@ -0,0 +1,203 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/interappaudio/iosEditor.mm
// Created by : Steinberg, 08/2013
// Description :
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Steinberg Media Technologies nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#import "iosEditor.h"
#import "public.sdk/source/vst/vsteditcontroller.h"
#import "adelayids.h"
using namespace Steinberg::Vst;
//------------------------------------------------------------------------
@interface ADelayViewController : UIViewController
{
EditController* editController;
}
@property (assign) IBOutlet UISlider* slider;
@end
//------------------------------------------------------------------------
@implementation ADelayViewController
//------------------------------------------------------------------------
- (id)initWithVstEditController:(EditController*)_editController
{
self = [super initWithNibName:@"ADelayIPAD" bundle:nil];
if (self)
{
editController = _editController;
}
return self;
}
//------------------------------------------------------------------------
- (void)updateSlider
{
[self.slider setValue:editController->getParamNormalized (kDelayId)];
}
//------------------------------------------------------------------------
- (IBAction)sliderChanged:(id)sender
{
editController->setParamNormalized (kDelayId, self.slider.value);
editController->performEdit(kDelayId, self.slider.value);
}
@end
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
ADelayEditorForIOS::ADelayEditorForIOS (EditController* editController)
: editController (editController)
, viewController (nil)
{
}
//------------------------------------------------------------------------
void PLUGIN_API ADelayEditorForIOS::update (FUnknown* changedUnknown, int32 message)
{
Parameter* param = FCast<Parameter> (changedUnknown);
if (param && viewController)
{
[viewController updateSlider];
}
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::isPlatformTypeSupported (FIDString type)
{
if (strcmp (type, kPlatformTypeUIView) == 0)
return kResultTrue;
return kResultFalse;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::attached (void* parent, FIDString type)
{
if (strcmp (type, kPlatformTypeUIView) != 0)
return kResultFalse;
UIView* parentView = (__bridge UIView*)parent;
viewController = [[ADelayViewController alloc] initWithVstEditController:editController];
if (viewController && viewController.view)
{
[parentView addSubview:viewController.view];
[viewController updateSlider];
Parameter* delayParam = editController->getParameterObject (kDelayId);
if (delayParam)
{
delayParam->addDependent (this);
}
}
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::removed ()
{
[viewController.view removeFromSuperview];
Parameter* delayParam = editController->getParameterObject (kDelayId);
if (delayParam)
{
delayParam->removeDependent (this);
}
viewController = nil;
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::onWheel (float distance)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::onKeyDown (char16 key, int16 keyCode, int16 modifiers)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::onKeyUp (char16 key, int16 keyCode, int16 modifiers)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::getSize (ViewRect* size)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::onSize (ViewRect* newSize)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::onFocus (TBool state)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::setFrame (IPlugFrame* frame)
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::canResize ()
{
return kNotImplemented;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ADelayEditorForIOS::checkSizeConstraint (ViewRect* rect)
{
return kNotImplemented;
}
}}
@@ -0,0 +1,35 @@
//------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Examples
// Filename : public.sdk/samples/vst/adelay/source/version.h
// Created by : Steinberg, 06/2009
// Description : Example of handle the versioning and copyright info of adelay plugin
// used for the resources (RC file for example)
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/base/fplatform.h"
// Plain project version file generated by cmake
#include "projectversion.h"
#define stringOriginalFilename "adelay.vst3"
#if SMTG_PLATFORM_64
#define stringFileDescription "ADelay VST3-SDK (64Bit)"
#else
#define stringFileDescription "ADelay VST3-SDK"
#endif
#define stringCompanyWeb "http://www.steinberg.net"
#define stringCompanyEmail "mailto:info@steinberg.de"
#define stringCompanyName "Steinberg Media Technologies"
#define stringLegalCopyright "© 2025 Steinberg Media Technologies"
#define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH"
@@ -0,0 +1,192 @@
cmake_minimum_required(VERSION 3.25.0)
project(smtg-vst3-again
VERSION ${vstsdk_VERSION}.0
DESCRIPTION "Steinberg VST 3 AGain example"
)
if(NOT SMTG_ENABLE_VSTGUI_SUPPORT)
return()
endif()
set(again_sources
source/again.cpp
source/again.h
source/againcids.h
source/againcontroller.cpp
source/againcontroller.h
source/againentry.cpp
source/againparamids.h
source/againprocess.h
source/againsidechain.cpp
source/againsidechain.h
source/againuimessagecontroller.h
source/version.h
resource/again.uidesc
)
set(again_simple_sources
${SDK_ROOT}/public.sdk/source/vst/vstsinglecomponenteffect.cpp
${SDK_ROOT}/public.sdk/source/vst/vstsinglecomponenteffect.h
source/againparamids.h
source/againsimple.cpp
source/againsimple.h
source/version.h
)
set(target again)
smtg_add_vst3plugin(${target} ${again_sources})
smtg_target_configure_version_file(${target})
#smtg_add_vst3plugin(${target} PACKAGE_NAME "A Gain" SOURCES_LIST ${again_sources})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_PLUGIN_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk
vstgui_support
)
smtg_target_add_plugin_resources(${target}
RESOURCES
resource/again.uidesc
resource/background.png
resource/slider_background.png
resource/slider_handle.png
resource/slider_handle_2.0x.png
resource/vu_on.png
resource/vu_off.png
)
smtg_target_add_plugin_snapshots (${target}
RESOURCES
resource/84E8DE5F92554F5396FAE4133C935A18_snapshot.png
resource/84E8DE5F92554F5396FAE4133C935A18_snapshot_2.0x.png
resource/41347FD6FED64094AFBB12B7DBA1D441_snapshot.png
resource/41347FD6FED64094AFBB12B7DBA1D441_snapshot_2.0x.png
)
if(SMTG_MAC)
smtg_target_set_bundle(${target}
BUNDLE_IDENTIFIER "com.steinberg.vst3.${target}"
COMPANY_NAME "Steinberg Media Technologies"
)
elseif(SMTG_WIN)
target_sources(${target}
PRIVATE
resource/again.rc
)
endif(SMTG_MAC)
# Add an AUv2 target
if (SMTG_MAC AND XCODE AND SMTG_ENABLE_AUV2_BUILDS)
include(SMTG_AddVST3AuV2)
smtg_target_add_auv2(again-au
BUNDLE_NAME again
BUNDLE_IDENTIFIER com.steinberg.vst3plugin.again.audiounit
INFO_PLIST_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/resource/au-info.plist
VST3_PLUGIN_TARGET again)
endif(SMTG_MAC AND XCODE AND SMTG_ENABLE_AUV2_BUILDS)
if(SMTG_MAC AND XCODE AND SMTG_ENABLE_IOS_TARGETS)
set(target again_ios)
smtg_add_ios_vst3plugin(${target} "${SMTG_CODE_SIGN_IDENTITY_IOS}" "${target}" "${again_sources}")
smtg_target_configure_version_file(${target})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_PLUGIN_EXAMPLES_FOLDER}
)
target_include_directories(${target}
PUBLIC
${SMTG_VSTGUI_SOURCE_DIR}
)
target_link_libraries(${target}
PRIVATE
base_ios
sdk_ios
"-framework UIKit"
"-framework CoreGraphics"
"-framework QuartzCore"
"-framework CoreText"
"-framework Accelerate"
"-framework ImageIO"
"-framework MobileCoreServices"
)
smtg_target_add_plugin_resources(${target}
RESOURCES
resource/again.uidesc
resource/background.png
resource/slider_background.png
resource/slider_handle.png
resource/slider_handle_2.0x.png
resource/vu_on.png
resource/vu_off.png
)
smtg_target_set_bundle(${target}
BUNDLE_IDENTIFIER "com.steinberg.vst3.again"
COMPANY_NAME "Steinberg Media Technologies"
)
target_sources(${target}
PRIVATE
${SMTG_VSTGUI_SOURCE_DIR}/vstgui/vstgui_uidescription.cpp
${SMTG_VSTGUI_SOURCE_DIR}/vstgui/vstgui_ios.mm
${SMTG_VSTGUI_SOURCE_DIR}/vstgui/plugin-bindings/vst3editor.cpp
${SDK_ROOT}/public.sdk/source/vst/vstguieditor.cpp
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
endif(SMTG_MAC AND XCODE AND SMTG_ENABLE_IOS_TARGETS)
set(targetsimple again-simple)
smtg_add_vst3plugin(${targetsimple} ${again_simple_sources})
smtg_target_configure_version_file(${targetsimple})
set_target_properties(${targetsimple}
PROPERTIES
${SDK_IDE_PLUGIN_EXAMPLES_FOLDER})
target_link_libraries(${targetsimple}
PUBLIC
sdk
vstgui_support
)
smtg_target_add_plugin_resources(${targetsimple}
RESOURCES
resource/again.uidesc
resource/background.png
resource/slider_background.png
resource/slider_handle.png
resource/slider_handle_2.0x.png
resource/vu_on.png
resource/vu_off.png
)
target_compile_features(${targetsimple}
PUBLIC
cxx_std_17
)
if(SMTG_MAC)
smtg_target_set_bundle(${targetsimple}
BUNDLE_IDENTIFIER "com.steinberg.vst3.again-simple"
COMPANY_NAME "Steinberg Media Technologies"
)
elseif(SMTG_WIN)
target_sources(${targetsimple}
PRIVATE
resource/again.rc
)
endif(SMTG_MAC)
@@ -0,0 +1,25 @@
# AGain VST 3
## Introduction
AGain VST 3 is the most simple FX plug-in with just one parameter for gain control.
There are several targets to show how the AGain plug-in can be built for different use cases.
* AGain VST 3
* AGain VST 3 Simple (SingleComponent approach)
* AGain AU (Audio Unit)
* AGain iOS
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/Plug-in+Examples.html#again).
## Getting Started
This plug-in is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Steinberg Media Technologies. All rights reserved.</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CSResourcesFileMapped</key>
<true/>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,45 @@
#include <windows.h>
#include "../source/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Version
/////////////////////////////////////////////////////////////////////////////
VS_VERSION_INFO VERSIONINFO
FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040004e4"
BEGIN
VALUE "FileVersion", FULL_VERSION_STR
VALUE "ProductVersion", FULL_VERSION_STR
VALUE "OriginalFilename", stringOriginalFilename
VALUE "FileDescription", stringFileDescription
VALUE "InternalName", stringFileDescription
VALUE "ProductName", stringFileDescription
VALUE "CompanyName", stringCompanyName
VALUE "LegalCopyright", stringLegalCopyright
VALUE "LegalTrademarks", stringLegalTrademarks
//VALUE "PrivateBuild", " \0"
//VALUE "SpecialBuild", " \0"
//VALUE "Comments", " \0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x400, 1252
END
END
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<vstgui-ui-description version="1">
<fonts>
</fonts>
<colors>
<color name="Focus" rgba="#279bffff"/>
<color name="Title Background" rgba="#39393944"/>
</colors>
<template background-color="~ BlackCColor" background-color-draw-style="filled and stroked" bitmap="background" class="CViewContainer" maxSize="350, 120" minSize="350, 120" mouse-enabled="true" name="view" opacity="1" origin="0, 0" size="350, 120" transparent="false">
<view back-color="Title Background" background-offset="0, 0" class="CTextLabel" default-value="0.5" font="~ NormalFontVeryBig" font-antialias="true" font-color="~ WhiteCColor" frame-color="Title Background" frame-width="1" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="10, 10" round-rect-radius="10" shadow-color="~ RedCColor" size="310, 28" style-3D-in="false" style-3D-out="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="true" style-shadow-text="false" text-alignment="center" text-inset="0, 0" text-rotation="0" title="VST3 AGain" transparent="false" value-precision="2" wheel-inc-value="0.1"/>
<view background-color="~ BlackCColor" background-color-draw-style="filled and stroked" class="CViewContainer" mouse-enabled="true" opacity="1" origin="0, 45" size="330, 38" sub-controller="MessageController" transparent="true">
<view back-color="~ BlackCColor" background-offset="0, 0" class="CTextLabel" default-value="0.5" font="~ NormalFont" font-antialias="true" font-color="~ WhiteCColor" frame-color="~ BlackCColor" frame-width="1" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="10, 10" round-rect-radius="6" shadow-color="~ BlackCColor" size="80, 18" style-3D-in="false" style-3D-out="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="false" style-shadow-text="true" text-alignment="left" text-inset="5, 0" text-rotation="0" title="IMessage:" transparent="true" value-precision="2" wheel-inc-value="0.1"/>
<view back-color="~ BlackCColor" background-offset="0, 0" class="CTextEdit" default-value="0.5" font="~ NormalFontSmall" font-antialias="true" font-color="~ WhiteCColor" frame-color="~ GreyCColor" frame-width="1" immediate-text-change="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="90, 10" round-rect-radius="6" shadow-color="~ RedCColor" size="130, 18" style-3D-in="false" style-3D-out="false" style-doubleclick="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="false" style-shadow-text="false" text-alignment="center" text-inset="0, 0" text-rotation="0" transparent="false" value-precision="2" wheel-inc-value="0.1"/>
<view background-offset="0, 0" class="CTextButton" control-tag="UI::SendMessage" default-value="0.5" font="~ SystemFont" frame-color="~ BlackCColor" frame-color-highlighted="~ BlackCColor" frame-width="1" gradient="Default TextButton Gradient" gradient-highlighted="Default TextButton Gradient Highlighted" icon-position="left" icon-text-margin="0" kick-style="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="230, 10" round-radius="6" size="90, 18" text-alignment="center" text-color="~ BlackCColor" text-color-highlighted="~ WhiteCColor" title="Send!" transparent="false" wheel-inc-value="0.1"/>
</view>
<view back-color="~ BlackCColor" background-offset="0, 0" class="CTextLabel" default-value="0.5" font="~ NormalFont" font-antialias="true" font-color="~ WhiteCColor" frame-color="~ BlackCColor" frame-width="1" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="10, 90" round-rect-radius="6" shadow-color="~ BlackCColor" size="80, 18" style-3D-in="false" style-3D-out="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="false" style-shadow-text="true" text-alignment="left" text-inset="5, 0" text-rotation="0" title="Gain:" transparent="true" value-precision="2" wheel-inc-value="0.1"/>
<view background-offset="0, 0" bitmap="slider_background" bitmap-offset="0, 0" class="CSlider" control-tag="Unit1::Gain" default-value="0.5" draw-back="false" draw-back-color="~ WhiteCColor" draw-frame="false" draw-frame-color="~ WhiteCColor" draw-value="false" draw-value-color="~ WhiteCColor" draw-value-from-center="false" draw-value-inverted="false" handle-bitmap="slider_handle" handle-offset="0, 2" max-value="1" min-value="0" mode="free click" mouse-enabled="true" opacity="1" orientation="horizontal" origin="90, 90" reverse-orientation="false" size="130, 18" transparent="false" transparent-handle="true" wheel-inc-value="0.1" zoom-factor="10"/>
<view back-color="~ BlackCColor" background-offset="0, 0" class="CTextEdit" control-tag="Unit1::Gain" default-value="0.5" font="~ NormalFontSmall" font-antialias="true" font-color="~ WhiteCColor" frame-color="~ GreyCColor" frame-width="1" immediate-text-change="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="230, 90" round-rect-radius="6" shadow-color="~ BlackCColor" size="90, 18" style-3D-in="false" style-3D-out="false" style-doubleclick="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="true" style-shadow-text="false" text-alignment="center" text-inset="5, 0" text-rotation="0" title="0.00" transparent="false" value-precision="2" wheel-inc-value="0.1"/>
<view background-offset="0, 0" bitmap="vu_on" class="CVuMeter" control-tag="Root::VuPPM" decrease-step-value="0.1" default-value="0" max-value="1" min-value="0" mouse-enabled="false" num-led="100" off-bitmap="vu_off" opacity="1" orientation="vertical" origin="330, 7" size="12, 105" transparent="false" wheel-inc-value="0.1"/>
</template>
<variables/>
<custom>
<attributes color="Focus" enabled="true" name="FocusDrawing" width="1"/>
<attributes Size="5, 5" name="UIGridController"/>
<attributes SelectedTemplate="view" name="UITemplateController"/>
<attributes EditViewScale="1" EditorSize="0, 0, 882, 716" SplitViewSize_0_0="0.6402877697841726778449356061173602938652" SplitViewSize_0_1="0.3309352517985611474848894886235939338803" SplitViewSize_1_0="0.506474820143884896239683257590513676405" SplitViewSize_1_1="0.486330935251798546214985208280268125236" SplitViewSize_2_0="0.636054421768707523021646466077072545886" SplitViewSize_2_1="0.3582766439909297329080573035753332078457" TabSwitchValue="0" Version="1" name="UIEditController"/>
<attributes name="UIAttributesController"/>
<attributes SelectedRow="23" name="UIViewCreatorDataSource"/>
<attributes SelectedRow="4" name="UIBitmapsDataSource"/>
<attributes Path="/Volumes/git_brauhaus/VST3Linux/public.sdk/samples/vst/again/resource/again.uidesc" name="VST3Editor"/>
<attributes SelectedRow="2" name="UITagsDataSource"/>
<attributes SelectedRow="0" name="UIColorsDataSource"/>
<attributes SelectedRow="-1" name="UIGradientsDataSource"/>
<attributes SelectedRow="-1" name="UIFontsDataSource"/>
</custom>
<bitmaps>
<bitmap name="background" path="background.png"/>
<bitmap name="slider_background" path="slider_background.png"/>
<bitmap name="slider_handle" path="slider_handle.png"/>
<bitmap name="slider_handle#2x" path="slider_handle_2.0x.png" scale-factor="2"/>
<bitmap name="vu_off" path="vu_off.png"/>
<bitmap name="vu_on" path="vu_on.png"/>
</bitmaps>
<control-tags>
<control-tag name="Root::Bypass" tag="2"/>
<control-tag name="Root::VuPPM" tag="1"/>
<control-tag name="UI::SendMessage" tag="1000"/>
<control-tag name="Unit1::Gain" tag="0"/>
</control-tags>
<gradients>
<gradient name="Default TextButton Gradient">
<color-stop rgba="#dcdcdcff" start="0"/>
<color-stop rgba="#b4b4b4ff" start="1"/>
</gradient>
<gradient name="Default TextButton Gradient Highlighted">
<color-stop rgba="#b4b4b4ff" start="0"/>
<color-stop rgba="#646464ff" start="1"/>
</gradient>
</gradients>
</vstgui-ui-description>

Some files were not shown because too many files have changed in this diff Show More