diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22b1366af8..f5839e40d8 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,10 @@ if(TARGET score_plugin_media) add_subdirectory(vstpuppet) endif() +if(TARGET score_plugin_vst3) + add_subdirectory(vst3puppet) +endif() + add_subdirectory(app) if(SCORE_PLAYER) diff --git a/src/vst3puppet/CMakeLists.txt b/src/vst3puppet/CMakeLists.txt new file mode 100644 index 0000000000..e7ffa2f7ad --- /dev/null +++ b/src/vst3puppet/CMakeLists.txt @@ -0,0 +1,82 @@ +project(vst3puppet CXX) +find_package(${QT_VERSION} OPTIONAL_COMPONENTS WebSockets) + +set(VST3_SDK_ROOT "${3RDPARTY_FOLDER}/vst3") +if(NOT TARGET ${QT_PREFIX}::WebSockets) + message("VST loading requires QtWebSockets.") + return() +endif() +add_executable(ossia-score-vst3puppet vst3puppet.cpp) + + +if(WIN32) + target_sources(ossia-score-vst3puppet PRIVATE + "${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp" + ) +elseif(APPLE) + target_sources(ossia-score-vst3puppet PRIVATE + "${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" + ) +else() + target_sources(ossia-score-vst3puppet PRIVATE + "${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp" + ) +endif() + +target_compile_definitions(ossia-score-vst3puppet PUBLIC HAS_VST3) +target_link_libraries( + ossia-score-vst3puppet + PRIVATE + ${QT_PREFIX}::Core + ${QT_PREFIX}::Gui + ${QT_PREFIX}::WebSockets + ${CMAKE_DL_LIBS} + sdk_common sdk_hosting + ) + +if(APPLE) + find_library(Foundation_FK Foundation) + target_link_libraries(ossia-score-vst3puppet PRIVATE + ${Foundation_FK} + ) +endif() + +target_include_directories( + ossia-score-vst3puppet + PRIVATE + "${SCORE_SRC}/plugins/score-plugin-media" +) + +setup_score_common_exe_features(ossia-score-vst3puppet) + +set_target_properties( + ossia-score-vst3puppet + PROPERTIES + DISABLE_PRECOMPILE_HEADERS TRUE +) + +if(APPLE AND DEPLOYMENT_BUILD) + set_target_properties( + ossia-score-vst3puppet + PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in" + RUNTIME_OUTPUT_DIRECTORY score.app/Contents/MacOS) + install( + TARGETS ossia-score-vst3puppet + BUNDLE DESTINATION score.app/Contents/MacOS + COMPONENT OssiaScore) +elseif(WIN32) + install( + TARGETS ossia-score-vst3puppet + RUNTIME DESTINATION . + COMPONENT OssiaScore) +else() + install( + TARGETS ossia-score-vst3puppet + RUNTIME DESTINATION bin + COMPONENT OssiaScore) +endif() + +disable_qt_plugins(ossia-score-vst3puppet) +enable_minimal_qt_plugins(ossia-score-vst3puppet) diff --git a/src/vst3puppet/Info.plist.in b/src/vst3puppet/Info.plist.in new file mode 100644 index 0000000000..58095a0cf0 --- /dev/null +++ b/src/vst3puppet/Info.plist.in @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ossia-score-vst3puppet + CFBundleGetInfoString + ossia-score-vst3puppet + CFBundleIdentifier + ossia-score-vst3puppet + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + 1.0 + CFBundleName + ossia-score-vst3puppet + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + CSResourcesFileMapped + + LSRequiresCarbon + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + LSUIElement + 1 + NSHumanReadableCopyright + ossia score + + diff --git a/src/vst3puppet/entitlements.plist b/src/vst3puppet/entitlements.plist new file mode 100644 index 0000000000..4f4a998976 --- /dev/null +++ b/src/vst3puppet/entitlements.plist @@ -0,0 +1,38 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.app-sandbox + + com.apple.security.cs.disable-library-validation + + com.apple.security.assets.music.read-write + + com.apple.security.files.downloads.read-write + + com.apple.security.device.firewire + + com.apple.security.device.microphone + + com.apple.security.device.usb + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.device.bluetooth + + com.apple.security.network.server + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.bookmarks.app-scope + + + diff --git a/src/vst3puppet/vst3puppet.cpp b/src/vst3puppet/vst3puppet.cpp new file mode 100644 index 0000000000..f36e9727b0 --- /dev/null +++ b/src/vst3puppet/vst3puppet.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace Steinberg; +QString load_vst(const QString& path, int id) +{ + try + { + bool isFile = QFile(QUrl(path).toString(QUrl::PreferLocalFile)).exists(); + if (!isFile) + { + std::cerr << "Invalid path: " << path.toStdString() << std::endl; + return {}; + } + + std::string err; + auto module = VST3::Hosting::Module::create(path.toStdString(), err); + + if (!module) + { + std::cerr << "Failed to load VST3 " << path.toStdString() << err << std::endl; + } + + const auto& info = module->getFactory().info(); + QJsonArray arr; + for(const auto& cls : module->getFactory().classInfos()) + { + if (cls.category() == kVstAudioEffectClass) + { + QJsonObject obj; + + obj["Author"] = QString::fromStdString(cls.vendor()); + obj["PrettyName"] = QString::fromStdString(cls.name()); + obj["Subcategories"] = QString::fromStdString(cls.subCategoriesString()); + obj["Version"] = QString::fromStdString(cls.version()); + obj["UID"] = QString::fromStdString(cls.ID().toString()); + obj["Path"] = path; + obj["Request"] = id; + + arr.push_back(obj); + } + } + return QJsonDocument{arr}.toJson(); + } + catch (const std::runtime_error& e) + { + std::cerr << e.what() << std::endl; + } + return {}; +} + +int main(int argc, char** argv) +{ + if (argc > 1) + { + int id = 0; + if(argc > 2) { + id = QString(argv[2]).toInt(); + } + QGuiApplication app(argc, argv); + QWindow w; + w.setWidth(1); + w.setHeight(1); + w.setFlag(Qt::FramelessWindowHint); + w.setFlag(Qt::X11BypassWindowManagerHint); + w.show(); + + QWebSocket socket; + + bool socket_ready{}, vst_ready{}; + QString json_ret; + + auto onReady = [&] { + if(socket_ready && vst_ready) { + socket.sendTextMessage(json_ret); + socket.flush(); + socket.close(); + app.exit(json_ret.isEmpty() ? 1 : 0); + } + }; + + QTimer::singleShot(32, [&] { + json_ret = load_vst(argv[1], id); + std::cout << json_ret.toStdString(); + vst_ready = true; + onReady(); + }); + + QObject::connect(&socket, &QWebSocket::connected, + &app, [&] { + socket_ready = true; + onReady(); + }); + + QObject::connect(&socket, qOverload(&QWebSocket::error), &app, + [&] { qDebug() << socket.errorString(); app.exit(1); }); + QObject::connect(&socket, &QWebSocket::disconnected, &app, + [&] { qDebug() << socket.errorString(); app.exit(1); }); + + QTimer::singleShot(10000, [&] { qDebug() << "timeout"; qApp->exit(1); }); + + socket.open(QUrl("ws://127.0.0.1:37587")); + app.exec(); + } + return 1; +}