Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/r0-2-hosted-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: R0-2 HostedPlugin Characterization

on:
pull_request:
paths:
- '.github/workflows/r0-2-hosted-plugin.yml'
- 'tests/r0_2/**'
- 'src/host/hosted_plugin.cpp'
- 'src/host/hosted_plugin.hpp'
- 'src/host/hosted_plugin_types.hpp'
- 'src/host/process_block_view.hpp'
- 'src/host/vst3_engine.cpp'
- 'src/host/vst3_engine.hpp'
- 'src/common/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: r0-2-hosted-plugin-${{ github.ref }}
cancel-in-progress: true

jobs:
hosted-plugin-deep-seam:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Configure deterministic HostedPlugin characterization
run: cmake -S tests/r0_2 -B build/r0-2 -A x64
- name: Build stateful fixture and HostedPlugin characterization
run: cmake --build build/r0-2 --config Release --target r0-2-hosted-plugin-test --parallel
- name: Run lifecycle/state/process/source-contract characterization
run: ctest --test-dir build/r0-2 -C Release --output-on-failure
156 changes: 156 additions & 0 deletions src/host/hosted_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#ifdef _WIN32

#include "host/hosted_plugin.hpp"
#include "host/vst3_engine.hpp"

#include <memory>

namespace safevst3 {

struct HostedPlugin::Impl {
Vst3Engine engine;
};

HostedPlugin::HostedPlugin() : impl_(std::make_unique<Impl>()) {}
HostedPlugin::~HostedPlugin() = default;

bool HostedPlugin::open(const std::string& path,
const std::string& class_id,
std::uint32_t sample_rate,
std::uint32_t channels,
Steinberg::Vst::IComponentHandler* component_handler,
StartupPhaseSink* startup_phase_sink,
std::string& error)
{
return impl_->engine.open(path, class_id, sample_rate, channels,
component_handler, startup_phase_sink, error);
}

bool HostedPlugin::open(const std::string& path,
const std::string& class_id,
std::uint32_t sample_rate,
std::uint32_t channels,
Steinberg::Vst::IComponentHandler* component_handler,
std::string& error)
{
return impl_->engine.open(path, class_id, sample_rate, channels,
component_handler, error);
}

void HostedPlugin::close() noexcept
{
impl_->engine.close();
}

bool HostedPlugin::process(const ProcessBlockView& block) noexcept
{
return impl_->engine.process(block);
}

bool HostedPlugin::capture_state(PluginStateSnapshot& snapshot, std::string& error)
{
return impl_->engine.capture_state(snapshot, error);
}

bool HostedPlugin::restore_state(const PluginStateSnapshot& snapshot, std::string& error)
{
return impl_->engine.restore_state(snapshot, error);
}

bool HostedPlugin::refresh_latency_after_restart(std::string& error)
{
return impl_->engine.refresh_latency_after_restart(error);
}

bool HostedPlugin::reconfigure_io_after_restart(IoLayout& layout,
std::uint32_t& latency_samples,
std::string& error)
{
return impl_->engine.reconfigure_io_after_restart(layout, latency_samples, error);
}

bool HostedPlugin::queue_parameter(std::uint32_t id, double normalized) noexcept
{
return impl_->engine.queue_parameter(id, normalized);
}

bool HostedPlugin::queue_parameter_from_controller(std::uint32_t id,
double normalized) noexcept
{
return impl_->engine.queue_parameter_from_controller(id, normalized);
}

bool HostedPlugin::set_controller_parameter(std::uint32_t id, double normalized) noexcept
{
return impl_->engine.set_controller_parameter(id, normalized);
}

bool HostedPlugin::queue_processor_parameter(std::uint32_t id, double normalized) noexcept
{
return impl_->engine.queue_processor_parameter(id, normalized);
}

bool HostedPlugin::flush_parameter_changes() noexcept
{
return impl_->engine.flush_parameter_changes();
}

void HostedPlugin::refresh_parameter_values() noexcept
{
impl_->engine.refresh_parameter_values();
}

bool HostedPlugin::refresh_parameter_metadata(std::string& error)
{
return impl_->engine.refresh_parameter_metadata(error);
}

std::size_t HostedPlugin::take_parameter_updates(EngineParameterUpdate* destination,
std::size_t capacity) noexcept
{
return impl_->engine.take_parameter_updates(destination, capacity);
}

void HostedPlugin::set_component_handler(Steinberg::Vst::IComponentHandler* handler) noexcept
{
impl_->engine.set_component_handler(handler);
}

Steinberg::Vst::IEditController* HostedPlugin::edit_controller() const noexcept
{
return impl_->engine.edit_controller();
}

const std::string& HostedPlugin::plugin_name() const noexcept
{
return impl_->engine.plugin_name();
}

const std::string& HostedPlugin::loaded_class_id() const noexcept
{
return impl_->engine.loaded_class_id();
}

std::uint32_t HostedPlugin::latency_samples() const noexcept
{
return impl_->engine.latency_samples();
}

std::uint32_t HostedPlugin::process_context_requirements() const noexcept
{
return impl_->engine.process_context_requirements();
}

std::uint32_t HostedPlugin::unsupported_process_context_requirements() const noexcept
{
return impl_->engine.unsupported_process_context_requirements();
}

const std::vector<EngineParameter>& HostedPlugin::parameters() const noexcept
{
return impl_->engine.parameters();
}

} // namespace safevst3

#endif
92 changes: 92 additions & 0 deletions src/host/hosted_plugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#ifdef _WIN32

#include "common/io_restart_transaction.hpp"
#include "common/startup_error.hpp"
#include "common/state_snapshot.hpp"
#include "host/hosted_plugin_types.hpp"
#include "host/process_block_view.hpp"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace Steinberg::Vst {
class IComponentHandler;
class IEditController;
} // namespace Steinberg::Vst

namespace safevst3 {

// Deep helper-side owner for exactly one VST3 audio-effect instance.
//
// This is the protocol-neutral reuse boundary for Rack work. Single/Rack
// transport adapters, OBS recovery orchestration, shared-memory layouts and
// topology remain outside this object. The implementation deliberately wraps
// the already-qualified Vst3Engine during R0-2 so lifecycle/state behavior is
// reused rather than rewritten.
class HostedPlugin final {
public:
HostedPlugin();
~HostedPlugin();

HostedPlugin(const HostedPlugin&) = delete;
HostedPlugin& operator=(const HostedPlugin&) = delete;
HostedPlugin(HostedPlugin&&) = delete;
HostedPlugin& operator=(HostedPlugin&&) = delete;

bool open(const std::string& path,
const std::string& class_id,
std::uint32_t sample_rate,
std::uint32_t channels,
Steinberg::Vst::IComponentHandler* component_handler,
StartupPhaseSink* startup_phase_sink,
std::string& error);
bool open(const std::string& path,
const std::string& class_id,
std::uint32_t sample_rate,
std::uint32_t channels,
Steinberg::Vst::IComponentHandler* component_handler,
std::string& error);
void close() noexcept;

bool process(const ProcessBlockView& block) noexcept;

bool capture_state(PluginStateSnapshot& snapshot, std::string& error);
bool restore_state(const PluginStateSnapshot& snapshot, std::string& error);
bool refresh_latency_after_restart(std::string& error);
bool reconfigure_io_after_restart(IoLayout& layout,
std::uint32_t& latency_samples,
std::string& error);

bool queue_parameter(std::uint32_t id, double normalized) noexcept;
bool queue_parameter_from_controller(std::uint32_t id, double normalized) noexcept;
bool set_controller_parameter(std::uint32_t id, double normalized) noexcept;
bool queue_processor_parameter(std::uint32_t id, double normalized) noexcept;
bool flush_parameter_changes() noexcept;
void refresh_parameter_values() noexcept;
bool refresh_parameter_metadata(std::string& error);
std::size_t take_parameter_updates(EngineParameterUpdate* destination,
std::size_t capacity) noexcept;

void set_component_handler(Steinberg::Vst::IComponentHandler* handler) noexcept;
Steinberg::Vst::IEditController* edit_controller() const noexcept;

const std::string& plugin_name() const noexcept;
const std::string& loaded_class_id() const noexcept;
std::uint32_t latency_samples() const noexcept;
std::uint32_t process_context_requirements() const noexcept;
std::uint32_t unsupported_process_context_requirements() const noexcept;
const std::vector<EngineParameter>& parameters() const noexcept;

private:
struct Impl;
std::unique_ptr<Impl> impl_;
};

} // namespace safevst3

#endif
25 changes: 25 additions & 0 deletions src/host/hosted_plugin_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <cstdint>
#include <string>

namespace safevst3 {

// Protocol-neutral metadata owned by the hosted VST3 lifecycle seam. These
// types intentionally contain no Single transport or shared-memory fields.
struct EngineParameter {
std::uint32_t id = 0;
std::int32_t step_count = 0;
std::uint32_t flags = 0;
double default_normalized = 0.0;
double current_normalized = 0.0;
std::string title;
std::string units;
};

struct EngineParameterUpdate {
std::uint32_t id = 0;
double normalized = 0.0;
};

} // namespace safevst3
18 changes: 2 additions & 16 deletions src/host/vst3_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "common/process_context_policy.hpp"
#include "common/startup_error.hpp"
#include "common/state_snapshot.hpp"
#include "host/hosted_plugin_types.hpp"
#include "host/process_block_view.hpp"
#include "host/vst3_processing_compat.hpp"

Expand All @@ -29,21 +30,6 @@

namespace safevst3 {

struct EngineParameter {
std::uint32_t id = 0;
std::int32_t step_count = 0;
std::uint32_t flags = 0;
double default_normalized = 0.0;
double current_normalized = 0.0;
std::string title;
std::string units;
};

struct EngineParameterUpdate {
std::uint32_t id = 0;
double normalized = 0.0;
};

class Vst3Engine final : public LatencyRestartTarget, public IoRestartLifecycleTarget {
public:
Vst3Engine() = default;
Expand Down Expand Up @@ -186,4 +172,4 @@ class Vst3Engine final : public LatencyRestartTarget, public IoRestartLifecycleT

} // namespace safevst3

#endif
#endif
Loading
Loading