Open a basic baseview window for standalone target
This commit is contained in:
parent
6c84fec09e
commit
da86d1c6b1
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2008,6 +2008,7 @@ dependencies = [
|
||||||
"assert_no_alloc",
|
"assert_no_alloc",
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
|
"baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/resize)",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"clap-sys",
|
"clap-sys",
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -44,7 +44,7 @@ assert_process_allocs = ["dep:assert_no_alloc"]
|
||||||
# Enables an export target for standalone binaries through the
|
# Enables an export target for standalone binaries through the
|
||||||
# `nih_export_standalone()` function. Disabled by default, as this requires
|
# `nih_export_standalone()` function. Disabled by default, as this requires
|
||||||
# building additional dependencies for audio and MIDI handling.
|
# building additional dependencies for audio and MIDI handling.
|
||||||
standalone = []
|
standalone = ["dep:baseview"]
|
||||||
# Enables the `nih_export_vst3!()` macro. Enabled by default. This feature
|
# Enables the `nih_export_vst3!()` macro. Enabled by default. This feature
|
||||||
# exists mostly for GPL-compliance reasons, since even if you don't use the VST3
|
# exists mostly for GPL-compliance reasons, since even if you don't use the VST3
|
||||||
# wrapper you might otherwise still include a couple (unused) symbols from the
|
# wrapper you might otherwise still include a couple (unused) symbols from the
|
||||||
|
@ -75,11 +75,17 @@ raw-window-handle = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/note-off-event", optional = true }
|
|
||||||
widestring = "1.0.0-beta.1"
|
widestring = "1.0.0-beta.1"
|
||||||
|
|
||||||
|
# Used for the `assert_process_allocs` feature
|
||||||
assert_no_alloc = { version = "1.1", optional = true }
|
assert_no_alloc = { version = "1.1", optional = true }
|
||||||
|
|
||||||
|
# Used for the `standalone` feature
|
||||||
|
baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/resize", optional = true }
|
||||||
|
|
||||||
|
# Used for the `vst3` feature
|
||||||
|
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/note-off-event", optional = true }
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies]
|
[target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies]
|
||||||
libc = "0.2.124"
|
libc = "0.2.124"
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,8 @@ pub trait Editor: Send + Sync {
|
||||||
// otherwise be basically impossible to have this still be GUI-framework agnostic. Any
|
// otherwise be basically impossible to have this still be GUI-framework agnostic. Any
|
||||||
// callback that deos involve actual GUI operations will still be spooled to the IRunLoop
|
// callback that deos involve actual GUI operations will still be spooled to the IRunLoop
|
||||||
// instance.
|
// instance.
|
||||||
|
// TODO: This function should return an `Option` instead. Right now window opening failures are
|
||||||
|
// always fatal. This would need to be fixed in basevie first.
|
||||||
fn spawn(
|
fn spawn(
|
||||||
&self,
|
&self,
|
||||||
parent: ParentWindowHandle,
|
parent: ParentWindowHandle,
|
||||||
|
|
|
@ -65,6 +65,9 @@ pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = Stri
|
||||||
sample_rate: 44100.0,
|
sample_rate: 44100.0,
|
||||||
period_size: 512,
|
period_size: 512,
|
||||||
|
|
||||||
|
// TODO: When adding command line options, ignore this option on macOS
|
||||||
|
dpi_scale: 1.0,
|
||||||
|
|
||||||
tempo: 120.0,
|
tempo: 120.0,
|
||||||
timesig_num: 4,
|
timesig_num: 4,
|
||||||
timesig_denom: 4,
|
timesig_denom: 4,
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::wrapper::Wrapper;
|
use super::wrapper::Wrapper;
|
||||||
use crate::context::{PluginApi, ProcessContext, Transport};
|
use crate::context::{GuiContext, PluginApi, ProcessContext, Transport};
|
||||||
use crate::midi::NoteEvent;
|
use crate::midi::NoteEvent;
|
||||||
|
use crate::param::internals::ParamPtr;
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
|
||||||
|
/// A [`GuiContext`] implementation for the wrapper. This is passed to the plugin in
|
||||||
|
/// [`Editor::spawn()`][crate::prelude::Editor::spawn()] so it can interact with the rest of the plugin and
|
||||||
|
/// with the host for things like setting parameters.
|
||||||
|
pub(crate) struct WrapperGuiContext<P: Plugin> {
|
||||||
|
pub(super) wrapper: Arc<Wrapper<P>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it
|
/// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it
|
||||||
/// can hold on to lock guards for event queues. Otherwise reading these events would require
|
/// can hold on to lock guards for event queues. Otherwise reading these events would require
|
||||||
/// constant unnecessary atomic operations to lock the uncontested RwLocks.
|
/// constant unnecessary atomic operations to lock the uncontested RwLocks.
|
||||||
|
@ -14,6 +24,40 @@ pub(crate) struct WrapperProcessContext<'a, P: Plugin> {
|
||||||
pub(super) transport: Transport,
|
pub(super) transport: Transport,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: Plugin> GuiContext for WrapperGuiContext<P> {
|
||||||
|
fn plugin_api(&self) -> PluginApi {
|
||||||
|
PluginApi::Standalone
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_resize(&self) -> bool {
|
||||||
|
nih_debug_assert_failure!("TODO: WrapperGuiContext::request_resize()");
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// All of these functions are supposed to be called from the main thread, so we'll put some
|
||||||
|
// trust in the caller and assume that this is indeed the case
|
||||||
|
unsafe fn raw_begin_set_parameter(&self, param: ParamPtr) {
|
||||||
|
nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_begin_set_parameter()");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32) {
|
||||||
|
nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_set_parameter_normalized()");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn raw_end_set_parameter(&self, param: ParamPtr) {
|
||||||
|
nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_end_set_parameter()");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state(&self) -> crate::wrapper::state::PluginState {
|
||||||
|
todo!("WrapperGuiContext::get_state()");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&self, state: crate::wrapper::state::PluginState) {
|
||||||
|
nih_debug_assert_failure!("TODO: WrapperGuiContext::set_state()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Plugin> ProcessContext for WrapperProcessContext<'_, P> {
|
impl<P: Plugin> ProcessContext for WrapperProcessContext<'_, P> {
|
||||||
fn plugin_api(&self) -> PluginApi {
|
fn plugin_api(&self) -> PluginApi {
|
||||||
PluginApi::Standalone
|
PluginApi::Standalone
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use raw_window_handle::HasRawWindowHandle;
|
||||||
|
use std::any::Any;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::context::WrapperProcessContext;
|
use super::context::{WrapperGuiContext, WrapperProcessContext};
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, Editor, Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin};
|
||||||
|
|
||||||
/// Configuration for a standalone plugin that would normally be provided by the DAW.
|
/// Configuration for a standalone plugin that would normally be provided by the DAW.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -17,6 +20,12 @@ pub struct WrapperConfig {
|
||||||
/// The audio backend's period size.
|
/// The audio backend's period size.
|
||||||
pub period_size: u32,
|
pub period_size: u32,
|
||||||
|
|
||||||
|
/// The editor's DPI scaling factor. Currently baseview has no way to report this to us, so
|
||||||
|
/// we'll expose it as a command line option instead.
|
||||||
|
///
|
||||||
|
/// This option is ignored on macOS.
|
||||||
|
pub dpi_scale: f32,
|
||||||
|
|
||||||
/// The current tempo.
|
/// The current tempo.
|
||||||
pub tempo: f32,
|
pub tempo: f32,
|
||||||
/// The time signature's numerator.
|
/// The time signature's numerator.
|
||||||
|
@ -31,7 +40,7 @@ pub struct Wrapper<P: Plugin> {
|
||||||
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need
|
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need
|
||||||
/// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
|
/// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
|
||||||
/// creating an editor.
|
/// creating an editor.
|
||||||
editor: Option<Box<dyn Editor>>,
|
editor: Option<Arc<dyn Editor>>,
|
||||||
|
|
||||||
config: WrapperConfig,
|
config: WrapperConfig,
|
||||||
|
|
||||||
|
@ -49,12 +58,28 @@ pub enum WrapperError {
|
||||||
InitializationFailed,
|
InitializationFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We need a window handler for baseview, but since we only create a window to report the plugin in
|
||||||
|
/// this won't do much.
|
||||||
|
struct WrapperWindowHandler {
|
||||||
|
/// The editor handle for the plugin's open editor. The editor should clean itself up when it
|
||||||
|
/// gets dropped.
|
||||||
|
_editor_handle: Box<dyn Any>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowHandler for WrapperWindowHandler {
|
||||||
|
fn on_frame(&mut self, _window: &mut Window) {}
|
||||||
|
|
||||||
|
fn on_event(&mut self, _window: &mut Window, _event: baseview::Event) -> EventStatus {
|
||||||
|
EventStatus::Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Plugin> Wrapper<P> {
|
impl<P: Plugin> Wrapper<P> {
|
||||||
/// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does
|
/// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does
|
||||||
/// not accept the IO configuration from the wrapper config.
|
/// not accept the IO configuration from the wrapper config.
|
||||||
pub fn new(config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
|
pub fn new(config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
|
||||||
let plugin = P::default();
|
let plugin = P::default();
|
||||||
let editor = plugin.editor();
|
let editor = plugin.editor().map(Arc::from);
|
||||||
|
|
||||||
let wrapper = Arc::new(Wrapper {
|
let wrapper = Arc::new(Wrapper {
|
||||||
plugin: RwLock::new(plugin),
|
plugin: RwLock::new(plugin),
|
||||||
|
@ -97,13 +122,63 @@ impl<P: Plugin> Wrapper<P> {
|
||||||
/// Will return an error if the plugin threw an error during audio processing or if the editor
|
/// Will return an error if the plugin threw an error during audio processing or if the editor
|
||||||
/// could not be opened.
|
/// could not be opened.
|
||||||
pub fn run(self: Arc<Self>) -> Result<(), WrapperError> {
|
pub fn run(self: Arc<Self>) -> Result<(), WrapperError> {
|
||||||
// TODO: Open the editor and block until it is closed
|
// TODO: Do IO things, kinda important
|
||||||
// TODO: Do IO things
|
|
||||||
// TODO: Block until SIGINT is received if the plugin does not have an editor
|
match self.editor.clone() {
|
||||||
|
Some(editor) => {
|
||||||
|
let context = self.clone().make_gui_context();
|
||||||
|
let (width, height) = editor.size();
|
||||||
|
|
||||||
|
// DPI scaling should not be used on macOS since the OS handles it there
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let scaling_policy = baseview::WindowScalePolicy::SystemScaleFactor;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let scaling_policy = {
|
||||||
|
editor.set_scale_factor(self.config.dpi_scale);
|
||||||
|
baseview::WindowScalePolicy::ScaleFactor(self.config.dpi_scale as f64)
|
||||||
|
};
|
||||||
|
|
||||||
|
Window::open_blocking(
|
||||||
|
WindowOpenOptions {
|
||||||
|
title: String::from(P::NAME),
|
||||||
|
size: baseview::Size {
|
||||||
|
width: width as f64,
|
||||||
|
height: height as f64,
|
||||||
|
},
|
||||||
|
scale: scaling_policy,
|
||||||
|
gl_config: None,
|
||||||
|
},
|
||||||
|
move |window| {
|
||||||
|
// TODO: This spawn function should be able to fail and return an error, but
|
||||||
|
// baseview does not support this yet. Once this is added, we should
|
||||||
|
// immediately close the parent window when this happens so the loop
|
||||||
|
// can exit.
|
||||||
|
let editor_handle = editor.spawn(
|
||||||
|
ParentWindowHandle {
|
||||||
|
handle: window.raw_window_handle(),
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
WrapperWindowHandler {
|
||||||
|
_editor_handle: editor_handle,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// TODO: Block until SIGINT is received if the plugin does not have an editor
|
||||||
|
todo!("Support standalone plugins without editors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_gui_context(self: Arc<Self>) -> Arc<WrapperGuiContext<P>> {
|
||||||
|
Arc::new(WrapperGuiContext { wrapper: self })
|
||||||
|
}
|
||||||
|
|
||||||
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
|
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
|
||||||
WrapperProcessContext {
|
WrapperProcessContext {
|
||||||
wrapper: self,
|
wrapper: self,
|
||||||
|
|
Loading…
Reference in a new issue