Open GavinRay97 opened 3 years ago
I would recommend not relying at all on reaper-high
and reaper-rx
. It's going to be very different in future. If you want I can show you how to achieve this using reaper-medium
. Okay?
Sounds good, you know the inner workings best 👍
I'm also interested in how to do this with reaper-medium.
I'll try to create a minimal medium-level-API-only example project soon. Until then, check below references. The only thing that you should use from the high-level API is the entry point mechanism:
#[reaper_extension_plugin(
name = "reaper-rs test extension plug-in",
support_email_address = "info@helgoboss.org"
)]
fn main() -> Result<(), Box<dyn Error>> {
// Just obtain the high-level REAPER instance and immediately go down to medium level.
let reaper_session = Reaper::get().medium_session();
reaper_session.reaper().show_console_msg("Hello world!");
Ok(())
}
Important: Don't look on crates.io or docs.rs, look here on the master branch.
reaper_medium::ReaperSession::audio_reg_hardware_hook_add
Rustdoc example.reaper_medium::ReaperSession::plugin_register_add_csurf_inst
Rustdoc example.reaper_medium::ReaperSession::plugin_register_add_hook_post_command
reaper_medium::ReaperSession::plugin_register_add_hook_post_command_2
Ahh okay got it, so it's just a regular csurf
implementation, and then calling the plugin_register('hook_post_command', fn_ptr)
Rust wrappers same as you'd normally do?
Thank you!
Yes. The goal of the medium-level API is to be 1:1 with the original API, just more safe and with idiomatic Rust. It's the hard part. Building something more convenient and more Rusty on top of that is easy ... but it's also very opinionated, that's why I have not settled on one way of doing things yet. Some of my abstractions are based on Rx (reaper-rx), some on enum variants (the "middleware" things in reaper-high).
Control surface callbacks
* This is the main way to get notified about most things. Just implement the desired trait methods. * See `reaper_medium::ReaperSession::plugin_register_add_csurf_inst` Rustdoc example.
I'm trying to make a basic control surface plugin. Following the tips from this issue and the generated docs, I ended up with a plugin that REAPER loads including running the plugin_main
function. Also registering the control surface seems to work as I could use the debugger to verify the result from the plugin_register_add_csurf_inst
call.
However, none of the implemented trait methods of the ControlSurface seem to be called as neither the debugger stops at those breakpoints and the println!() doesn't show up in the output during a debug session.
Is there anything more I have to do to get the csurf traits working?
use std::error::Error;
use reaper_macros::reaper_extension_plugin;
use reaper_low::PluginContext;
use reaper_medium::{ReaperSession, ControlSurface, ReaperStr };
use std::ffi::CString;
#[derive(Debug)]
struct MyControlSurface;
impl ControlSurface for MyControlSurface {
fn set_track_list_change(&self) {
println!("Tracks changed");
}
fn get_type_string(&self) -> Option<&ReaperStr> {
let type_string = CString::new("ControlType").expect("Didn't work!");
let type_string_ptr = type_string.as_ptr();
unsafe{
let return_string = ReaperStr::from_ptr(type_string_ptr);
Some(return_string)
}
}
fn get_desc_string(&self) -> Option<&ReaperStr> {
let desc_string = CString::new("Control Description").expect("Didin't work!");
let desc_string_ptr = desc_string.as_ptr();
unsafe{
let return_string = ReaperStr::from_ptr(desc_string_ptr);
Some(return_string)
}
}
fn run(&mut self) {
println!("Running!");
}
}
#[reaper_extension_plugin]
fn plugin_main(context: PluginContext) -> Result<(), Box<dyn Error>> {
let mut session = ReaperSession::load(context);
let result = session.plugin_register_add_csurf_inst(Box::new(MyControlSurface));
match result {
Ok(_v) => session.reaper().show_console_msg("Csurf created successfully!\n"),
Err(_e) => session.reaper().show_console_msg("Creating csurf failed!"),
};
Ok(())
}
Is the problem that at the end of plugin_main
the registered control surface gets dropped? If so, how should we ensure that this doesn't happen?
@Schroedingers-Cat Sorry for the delay. Yes, the drop is the issue. Actually the complete ReaperSession
gets dropped and the control surface along with it. (Note how this doesn't crash REAPER though ... that's a safety/cleanup feature added by reaper-medium
:-) The only solution is to store the ReaperSession
in a static (which is what a C++ extension would do as well). Either you do that manually or you use a small part of reaper-high
to bootstrap the extension in a way that makes everything stay in memory. I recommend the latter because this is hopefully a part of the high-level API that's not going to change too much in future. It lets you access the high-level API from anywhere via static function reaper_high::Reaper::get()
. Since the high-level API is still very much in flux, I recommend to immediately go down to the medium-level API by calling medium_reaper()
on the high-level REAPER instance. That lets you call REAPER functions. If you need access to the medium-level session, you call medium_session()
instead. There you can register/unregister stuff and reaper-rs will do the memory management for you.
Thanks a lot for the explanations! I tried it via reaper-high
as you suggested and the implemented control surface callbacks seem to be working. Maybe it'd be helpful to others to add a minimal example to the docs like the following?
use std::error::Error;
use reaper_macros::reaper_extension_plugin;
use reaper_medium::ControlSurface;
#[derive(Debug)]
struct MyControlSurface;
impl ControlSurface for MyControlSurface {
// Implement Reaper's CSurf functions here
fn set_track_list_change(&self) {
reaper_high::Reaper::get().medium_reaper().show_console_msg("CSurf: Track list changed!\n");
}
}
#[reaper_extension_plugin(
name = "NAME_OF_YOUR_PLUGIN",
support_email_address = "SUPPORT_MAIL_ADDRESS"
)]
fn plugin_main() -> Result<(), Box<dyn Error>> {
let session = reaper_high::Reaper::get();
let result = session.medium_session().plugin_register_add_csurf_inst(Box::new(MyControlSurface));
match result {
Ok(_v) => session.medium_reaper().show_console_msg("Csurf created successfully!\n"),
Err(_e) => session.medium_reaper().show_console_msg("Creating csurf failed!"),
};
Ok(())
}
I'm Sorry, for several hours can't figure the way of registering the action (command).
I've tried as from high-level with, as I think, a static closure:
#[reaper_extension_plugin(
name = "NAME_OF_YOUR_PLUGIN",
support_email_address = "SUPPORT_MAIL_ADDRESS"
)]
fn plugin_main() -> Result<(), Box<dyn Error>> {
let session = reaper_high::Reaper::get();
session.register_action("test_reaper_ext", "description", ||reaper_high::Reaper::get().show_console_msg("action"), ActionKind::NotToggleable);
Ok(())
}
As well, as with medium-level by moving around previously noticed
reaper_medium::ReaperSession::plugin_register_add_hook_post_command
reaper_medium::ReaperSession::plugin_register_add_hook_post_command_2
Anyway, still I can not reach for any verbose error or panic, not to see the action in actions list.
P.S. I want to start with the usual "scripted" way of making the extension, as I want to continue the development of my python GUI plugin in rust for taking benefits of type system and speed.
Well, for now I've figured this with using also of reaper_high::Action
struct
use reaper_high;
use reaper_macros::reaper_extension_plugin;
use reaper_medium::{CommandId, HookCommand, OwnedGaccelRegister};
use std::error::Error;
const NAME: &str = "TEST_COMMAND";
fn id(name: &str) -> CommandId {
let session = reaper_high::Reaper::get();
let action = session.action_by_command_name(name);
action.command_id().unwrap()
}
struct MyHook {}
impl HookCommand for MyHook {
fn call(command_id: CommandId, _flag: i32) -> bool {
let id = id(NAME);
if command_id != id{
reaper_high::Reaper::get().show_console_msg(format!(
"wrong command, original is {}, got {}",
id, command_id
));
return false;
}
reaper_high::Reaper::get().show_console_msg("test");
true
}
}
#[reaper_extension_plugin(
name = "NAME_OF_YOUR_PLUGIN",
support_email_address = "SUPPORT_MAIL_ADDRESS"
)]
fn plugin_main() -> Result<(), Box<dyn Error>> {
let mut session = reaper_high::Reaper::get().medium_session();
let id = session.plugin_register_add_command_id(NAME)?;
session.plugin_register_add_gaccel(OwnedGaccelRegister::without_key_binding(
id,
"test_rust_ext",
))?;
session
.plugin_register_add_hook_command::<MyHook>()
.unwrap();
Ok(())
}
So I have pulled this out from referencing the below files, but I don't know enough about the project internals to tell whether this is the simplest and most-minimal implementation.
https://github.com/helgoboss/reaper-rs/blob/master/test/test/src/api.rs https://github.com/helgoboss/reaper-rs/blob/master/test/test/src/lib.rs
Can the example here be made any simpler? https://gist.github.com/GavinRay97/3195db1606d7e19344b23e3401583dc9
Also, few quick questions:
MidiRx
stuff currently inaccessible as a WIP? Seems to be private:static TEST: once_cell::sync::Lazy<Test> = once_cell::sync::Lazy::new(Default::default);
Is this necessary, or does it work with just
&Test::get()
like below?.subscribe()
the type definition for some reason is empty:Thank you =)