2022-04-23 03:22:23 +10:00
|
|
|
use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions};
|
2022-04-23 03:36:19 +10:00
|
|
|
use parking_lot::{Mutex, RwLock};
|
2022-04-23 03:22:23 +10:00
|
|
|
use raw_window_handle::HasRawWindowHandle;
|
|
|
|
use std::any::Any;
|
2022-04-23 02:29:00 +10:00
|
|
|
use std::sync::Arc;
|
2022-04-23 01:00:59 +10:00
|
|
|
|
2022-04-23 04:41:21 +10:00
|
|
|
use super::backend::Backend;
|
2022-04-23 03:22:23 +10:00
|
|
|
use super::context::{WrapperGuiContext, WrapperProcessContext};
|
2022-04-23 02:29:00 +10:00
|
|
|
use crate::context::Transport;
|
2022-04-23 03:22:23 +10:00
|
|
|
use crate::plugin::{BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin};
|
2022-04-23 02:29:00 +10:00
|
|
|
|
|
|
|
/// Configuration for a standalone plugin that would normally be provided by the DAW.
|
2022-04-23 02:38:32 +10:00
|
|
|
#[derive(Debug, Clone)]
|
2022-04-23 02:29:00 +10:00
|
|
|
pub struct WrapperConfig {
|
|
|
|
/// The number of input channels.
|
|
|
|
pub input_channels: u32,
|
|
|
|
/// The number of output channels.
|
|
|
|
pub output_channels: u32,
|
|
|
|
/// The audio backend's sample rate.
|
|
|
|
pub sample_rate: f32,
|
|
|
|
/// The audio backend's period size.
|
|
|
|
pub period_size: u32,
|
|
|
|
|
2022-04-23 03:22:23 +10:00
|
|
|
/// 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,
|
|
|
|
|
2022-04-23 02:29:00 +10:00
|
|
|
/// The current tempo.
|
|
|
|
pub tempo: f32,
|
|
|
|
/// The time signature's numerator.
|
|
|
|
pub timesig_num: u32,
|
|
|
|
/// The time signature's denominator.
|
|
|
|
pub timesig_denom: u32,
|
|
|
|
}
|
2022-04-23 01:00:59 +10:00
|
|
|
|
2022-04-23 04:41:21 +10:00
|
|
|
pub struct Wrapper<P: Plugin, B: Backend> {
|
|
|
|
backend: B,
|
|
|
|
|
2022-04-23 01:00:59 +10:00
|
|
|
/// The wrapped plugin instance.
|
|
|
|
plugin: RwLock<P>,
|
2022-04-23 02:54:39 +10:00
|
|
|
/// 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
|
|
|
|
/// creating an editor.
|
2022-04-23 03:36:19 +10:00
|
|
|
pub editor: Option<Arc<dyn Editor>>,
|
2022-04-23 02:29:00 +10:00
|
|
|
|
|
|
|
config: WrapperConfig,
|
|
|
|
|
|
|
|
/// The bus and buffer configurations are static for the standalone target.
|
|
|
|
bus_config: BusConfig,
|
|
|
|
buffer_config: BufferConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Errors that may arise while initializing the wrapped plugins.
|
2022-04-23 02:54:39 +10:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
2022-04-23 02:29:00 +10:00
|
|
|
pub enum WrapperError {
|
|
|
|
/// The plugin does not accept the IO configuration from the config.
|
|
|
|
IncompatibleConfig,
|
|
|
|
/// The plugin returned `false` during initialization.
|
|
|
|
InitializationFailed,
|
2022-04-23 01:00:59 +10:00
|
|
|
}
|
|
|
|
|
2022-04-23 03:22:23 +10:00
|
|
|
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>,
|
2022-04-23 03:36:19 +10:00
|
|
|
|
|
|
|
/// If contains a value, then the GUI will be resized at the start of the next frame. This is
|
|
|
|
/// set from [`WrapperGuiContext::request_resize()`].
|
|
|
|
new_window_size: Arc<Mutex<Option<(u32, u32)>>>,
|
2022-04-23 03:22:23 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WindowHandler for WrapperWindowHandler {
|
2022-04-23 03:36:19 +10:00
|
|
|
fn on_frame(&mut self, window: &mut Window) {
|
|
|
|
if let Some((new_width, new_height)) = self.new_window_size.lock().take() {
|
|
|
|
// Window resizing in baseview has only been implemented on Linux
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
{
|
|
|
|
window.resize(baseview::Size {
|
|
|
|
width: new_width as f64,
|
|
|
|
height: new_height as f64,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-23 03:22:23 +10:00
|
|
|
|
|
|
|
fn on_event(&mut self, _window: &mut Window, _event: baseview::Event) -> EventStatus {
|
|
|
|
EventStatus::Ignored
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 04:41:21 +10:00
|
|
|
impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
2022-04-23 02:29:00 +10:00
|
|
|
/// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does
|
|
|
|
/// not accept the IO configuration from the wrapper config.
|
2022-04-23 04:41:21 +10:00
|
|
|
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
|
2022-04-23 02:54:39 +10:00
|
|
|
let plugin = P::default();
|
2022-04-23 03:22:23 +10:00
|
|
|
let editor = plugin.editor().map(Arc::from);
|
2022-04-23 02:54:39 +10:00
|
|
|
|
2022-04-23 02:29:00 +10:00
|
|
|
let wrapper = Arc::new(Wrapper {
|
2022-04-23 04:41:21 +10:00
|
|
|
backend,
|
|
|
|
|
2022-04-23 02:54:39 +10:00
|
|
|
plugin: RwLock::new(plugin),
|
|
|
|
editor,
|
|
|
|
|
2022-04-23 02:29:00 +10:00
|
|
|
bus_config: BusConfig {
|
|
|
|
num_input_channels: config.input_channels,
|
|
|
|
num_output_channels: config.output_channels,
|
|
|
|
},
|
|
|
|
buffer_config: BufferConfig {
|
|
|
|
sample_rate: config.sample_rate,
|
|
|
|
max_buffer_size: config.period_size,
|
|
|
|
},
|
|
|
|
config,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Right now the IO configuration is fixed in the standalone target, so if the plugin cannot
|
|
|
|
// work with this then we cannot initialize the plugin at all.
|
|
|
|
{
|
|
|
|
let mut plugin = wrapper.plugin.write();
|
|
|
|
if !plugin.accepts_bus_config(&wrapper.bus_config) {
|
|
|
|
return Err(WrapperError::IncompatibleConfig);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !plugin.initialize(
|
|
|
|
&wrapper.bus_config,
|
|
|
|
&wrapper.buffer_config,
|
|
|
|
&mut wrapper.make_process_context(Transport::new(wrapper.config.sample_rate)),
|
|
|
|
) {
|
|
|
|
return Err(WrapperError::InitializationFailed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(wrapper)
|
|
|
|
}
|
|
|
|
|
2022-04-23 02:54:39 +10:00
|
|
|
/// Open the editor, start processing audio, and block this thread until the editor is closed.
|
|
|
|
/// If the plugin does not have an editor, then this will block until SIGINT is received.
|
|
|
|
///
|
|
|
|
/// Will return an error if the plugin threw an error during audio processing or if the editor
|
|
|
|
/// could not be opened.
|
|
|
|
pub fn run(self: Arc<Self>) -> Result<(), WrapperError> {
|
2022-04-23 03:22:23 +10:00
|
|
|
// TODO: Do IO things, kinda important
|
|
|
|
|
|
|
|
match self.editor.clone() {
|
|
|
|
Some(editor) => {
|
2022-04-23 03:36:19 +10:00
|
|
|
// We'll use this mutex to communicate window size changes. If we need to send a lot
|
|
|
|
// more information to the window handler at some point, then consider replacing
|
|
|
|
// this with a channel.
|
|
|
|
let new_window_size = Arc::new(Mutex::new(None));
|
|
|
|
let context = self.clone().make_gui_context(new_window_size.clone());
|
2022-04-23 03:22:23 +10:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
};
|
|
|
|
|
2022-04-23 03:36:19 +10:00
|
|
|
let (width, height) = editor.size();
|
2022-04-23 03:22:23 +10:00
|
|
|
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,
|
2022-04-23 03:36:19 +10:00
|
|
|
new_window_size,
|
2022-04-23 03:22:23 +10:00
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
// TODO: Block until SIGINT is received if the plugin does not have an editor
|
|
|
|
todo!("Support standalone plugins without editors");
|
|
|
|
}
|
|
|
|
}
|
2022-04-23 02:54:39 +10:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-04-23 03:36:19 +10:00
|
|
|
fn make_gui_context(
|
|
|
|
self: Arc<Self>,
|
|
|
|
new_window_size: Arc<Mutex<Option<(u32, u32)>>>,
|
2022-04-23 04:41:21 +10:00
|
|
|
) -> Arc<WrapperGuiContext<P, B>> {
|
2022-04-23 03:36:19 +10:00
|
|
|
Arc::new(WrapperGuiContext {
|
|
|
|
wrapper: self,
|
|
|
|
new_window_size,
|
|
|
|
})
|
2022-04-23 03:22:23 +10:00
|
|
|
}
|
|
|
|
|
2022-04-23 04:41:21 +10:00
|
|
|
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P, B> {
|
2022-04-23 02:29:00 +10:00
|
|
|
WrapperProcessContext {
|
|
|
|
wrapper: self,
|
|
|
|
transport,
|
2022-04-23 01:00:59 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|