2022-04-22 17:00:59 +02:00
|
|
|
//! A standalone plugin target that directly connects to the system's audio and MIDI ports instead
|
|
|
|
//! of relying on a plugin host. This is mostly useful for quickly testing GUI changes.
|
|
|
|
|
2022-06-14 15:44:42 +02:00
|
|
|
use clap::{FromArgMatches, IntoApp};
|
|
|
|
|
2022-04-22 18:38:32 +02:00
|
|
|
use self::wrapper::{Wrapper, WrapperConfig, WrapperError};
|
2022-04-24 15:50:17 +02:00
|
|
|
use super::util::setup_logger;
|
2022-04-22 17:00:59 +02:00
|
|
|
use crate::plugin::Plugin;
|
|
|
|
|
2022-04-22 20:41:21 +02:00
|
|
|
mod backend;
|
2022-04-22 18:29:00 +02:00
|
|
|
mod context;
|
2022-04-22 17:00:59 +02:00
|
|
|
mod wrapper;
|
|
|
|
|
|
|
|
/// Open an NIH-plug plugin as a standalone application. If the plugin has an editor, this will open
|
|
|
|
/// the editor and block until the editor is closed. Otherwise this will block until SIGINT is
|
2022-04-22 17:17:42 +02:00
|
|
|
/// received. This is mainly useful for quickly testing plugin GUIs. In order to use this, you will
|
|
|
|
/// first need to make your plugin's main struct `pub` and expose a `lib` artifact in addition to
|
|
|
|
/// your plugin's `cdylib`:
|
|
|
|
///
|
|
|
|
/// ```toml
|
|
|
|
/// # Cargo.toml
|
|
|
|
///
|
|
|
|
/// [lib]
|
|
|
|
/// # The `lib` artifact is needed for the standalone target
|
|
|
|
/// crate-type = ["cdylib", "lib"]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// You can then create a `src/main.rs` file that calls this function:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// // src/main.rs
|
|
|
|
///
|
|
|
|
/// use plugin_name::prelude::*;
|
|
|
|
///
|
|
|
|
/// use plugin_name::PluginName;
|
|
|
|
///
|
|
|
|
/// fn main() {
|
|
|
|
/// nih_export_standalone::<PluginName>();
|
|
|
|
/// }
|
|
|
|
/// ```
|
2022-04-22 17:00:59 +02:00
|
|
|
///
|
|
|
|
/// By default this will connect to the 'default' audio and MIDI ports. Use the command line options
|
|
|
|
/// to change this. `--help` lists all available options.
|
|
|
|
///
|
2022-04-22 18:38:32 +02:00
|
|
|
/// If the wrapped plugin fails to initialize or throws an error during audio processing, then this
|
|
|
|
/// function will return `false`.
|
|
|
|
pub fn nih_export_standalone<P: Plugin>() -> bool {
|
2022-04-22 17:00:59 +02:00
|
|
|
nih_export_standalone_with_args::<P, _>(std::env::args())
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:38:32 +02:00
|
|
|
/// The same as [`nih_export_standalone()`], but with the arguments taken from an iterator instead
|
|
|
|
/// of using [`std::env::args()`].
|
|
|
|
pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = String>>(
|
2022-06-14 15:44:42 +02:00
|
|
|
args: Args,
|
2022-04-22 18:38:32 +02:00
|
|
|
) -> bool {
|
2022-04-24 15:50:17 +02:00
|
|
|
setup_logger();
|
|
|
|
|
2022-06-14 15:44:42 +02:00
|
|
|
// Instead of parsing this directly, we need to take a bit of a roundabout approach to get the
|
|
|
|
// plugin's name and vendor in here since they'd otherwise be taken from NIH-plug's own
|
|
|
|
// `Cargo.toml` file.
|
|
|
|
let config = WrapperConfig::from_arg_matches(
|
|
|
|
&WrapperConfig::command()
|
|
|
|
.name(P::NAME)
|
|
|
|
.author(P::VENDOR)
|
|
|
|
.get_matches_from(args),
|
|
|
|
)
|
|
|
|
.unwrap_or_else(|err| err.exit());
|
2022-04-22 18:29:00 +02:00
|
|
|
|
2022-04-24 15:50:17 +02:00
|
|
|
nih_log!(
|
2022-04-22 21:07:47 +02:00
|
|
|
"Audio and MIDI IO has not yet been implemented in the standalone targets. So if you're \
|
|
|
|
not hearing anything, then that's correct!"
|
|
|
|
);
|
|
|
|
|
2022-04-22 20:41:21 +02:00
|
|
|
// TODO: We should try JACK first, then CPAL, and then fall back to the dummy backend. With a
|
|
|
|
// command line option to override this behavior.
|
|
|
|
let backend = backend::Dummy::new(config.clone());
|
|
|
|
let wrapper = match Wrapper::<P, _>::new(backend, config.clone()) {
|
2022-04-22 18:38:32 +02:00
|
|
|
Ok(wrapper) => wrapper,
|
2022-04-22 18:54:39 +02:00
|
|
|
Err(err) => {
|
|
|
|
print_error(err, &config);
|
2022-04-22 18:38:32 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
2022-04-22 17:00:59 +02:00
|
|
|
|
2022-06-14 15:44:42 +02:00
|
|
|
// TODO: Add a repl while the application is running to interact with parameters
|
2022-04-22 18:54:39 +02:00
|
|
|
match wrapper.run() {
|
|
|
|
Ok(()) => true,
|
|
|
|
Err(err) => {
|
|
|
|
print_error(err, &config);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-22 18:38:32 +02:00
|
|
|
|
2022-04-22 18:54:39 +02:00
|
|
|
fn print_error(error: WrapperError, config: &WrapperConfig) {
|
|
|
|
match error {
|
|
|
|
WrapperError::IncompatibleConfig => {
|
|
|
|
eprintln!("The plugin does not support the {} channel input and {} channel output configuration", config.input_channels, config.output_channels);
|
|
|
|
}
|
|
|
|
WrapperError::InitializationFailed => {
|
|
|
|
eprintln!("The plugin failed to initialize");
|
|
|
|
}
|
|
|
|
}
|
2022-04-22 17:00:59 +02:00
|
|
|
}
|