Initial release
This commit is contained in:
+24
@@ -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
@@ -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()
|
||||
+89
@@ -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)**
|
||||
+60
@@ -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>
|
||||
+158
@@ -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
|
||||
+54
@@ -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
|
||||
Vendored
+440
@@ -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
|
||||
+108
@@ -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
|
||||
+98
@@ -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
|
||||
Vendored
+39
@@ -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
|
||||
Vendored
+405
@@ -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
|
||||
+139
@@ -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
|
||||
+38
@@ -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
|
||||
+24
@@ -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
|
||||
+159
@@ -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)**
|
||||
+60
@@ -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>
|
||||
+426
@@ -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
|
||||
+61
@@ -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
|
||||
+40
@@ -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
|
||||
Vendored
+44
@@ -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
|
||||
Vendored
+52
@@ -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[]);
|
||||
+120
@@ -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
|
||||
engine/vendor/vst3sdk/public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h
Vendored
+135
@@ -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
|
||||
Vendored
+205
@@ -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
|
||||
Vendored
+280
@@ -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
|
||||
Vendored
+110
@@ -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
|
||||
+493
@@ -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, ®istry_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, ®istry_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
|
||||
+61
@@ -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
|
||||
+375
@@ -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
|
||||
+285
@@ -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
|
||||
+56
@@ -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
|
||||
+256
@@ -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
|
||||
+105
@@ -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
|
||||
+594
@@ -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
|
||||
+93
@@ -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
|
||||
Vendored
+626
@@ -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
|
||||
Vendored
+69
@@ -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
|
||||
Vendored
+222
@@ -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);
|
||||
}
|
||||
Vendored
+60
@@ -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
|
||||
Vendored
+209
@@ -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
|
||||
Vendored
+163
@@ -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;
|
||||
}
|
||||
Vendored
+298
@@ -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
|
||||
Vendored
+220
@@ -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
|
||||
+24
@@ -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
|
||||
+75
@@ -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)**
|
||||
+26
@@ -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>
|
||||
+1512
File diff suppressed because it is too large
Load Diff
+50
@@ -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
|
||||
+500
@@ -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
|
||||
+27
@@ -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
|
||||
+159
@@ -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)**
|
||||
+86
@@ -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
|
||||
+109
@@ -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
|
||||
+29
@@ -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)
|
||||
}
|
||||
+837
@@ -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
|
||||
+111
@@ -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)
|
||||
+64
@@ -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)
|
||||
+386
@@ -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)
|
||||
Vendored
+65
@@ -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>
|
||||
+26
@@ -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
|
||||
+8
@@ -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>
|
||||
+26
@@ -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
|
||||
+119
@@ -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
|
||||
+99
@@ -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>
|
||||
+34
@@ -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
|
||||
|
||||
+116
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
+26
@@ -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
|
||||
|
||||
+90
@@ -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
|
||||
+70
@@ -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
|
||||
+226
@@ -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
|
||||
+31
@@ -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>
|
||||
+85
@@ -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__
|
||||
+203
@@ -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>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
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
Reference in New Issue
Block a user