helgoboss / reaper-rs

Rust bindings for the REAPER C++ API
MIT License
80 stars 8 forks source link

Please add the `InsertMedia` function #31

Closed Boscop closed 1 year ago

Boscop commented 3 years ago

Can you please add bindings for the InsertMedia function, it would be very useful :) My use case is, I'm experimenting with midi livecoding in Rust and I want to integrate it into Reaper as a VST. The only way to edit the arrangement via the API seems to be via InsertMedia: Every time the composition changes, writing the midi from memory into a file on disk and then calling InsertMedia. (Or can you see another way?)

There are 2 related functions: https://www.reaper.fm/sdk/reascript/reascripthelp.html#InsertMedia https://www.reaper.fm/sdk/reascript/reascripthelp.html#InsertMediaSection

But for my use case I only need InsertMedia, because every call will overwrite the full composition.


CC @PrismaPhonic https://github.com/helgoboss/reaper-rs/issues/18

helgoboss commented 3 years ago

There are ways to do this more directly and I think it would involve functions like AddMediaItemToTrack, AddTakeToMediaItem, CreateNewMIDIItemInProj etc. However, it's not super straightforward and it would probably need more time to turn this into good idiomatic Rust. InsertMedia on the other hand should be very easy to add to the medium-level API. If that's enough for you, I could give it a go.

Boscop commented 3 years ago

Yes, I think having InsertMedia would be sufficient for now. Thanks :)


It seems the other way (CreateNewMIDIItemInProj etc.) requires bindings to many more functions, to be able to populate/modify the newly created media item.

Boscop commented 3 years ago

I just realized that the VST also needs to clear the track before adding the new media item, so I'd also need these functions (to iterate over all items and delete each): https://www.reaper.fm/sdk/reascript/reascripthelp.html#GetTrackNumMediaItems https://www.reaper.fm/sdk/reascript/reascripthelp.html#GetTrackMediaItem https://www.reaper.fm/sdk/reascript/reascripthelp.html#DeleteTrackMediaItem

(I could add it on a new track but that would not be idempotent behavior. Also the midi track's FX and routings should survive the "midi file reload".)


Btw, can non-VST Reaper plugin DLLs run in the background, or are they only ran on demand like scripts (and need to be compiled as a VST if they should stay running in the background)? I only used the Reaper extension API from inside a VST or from a LUA script until now, so I'm wondering if I should compile this one as a VST or as a normal DLL (that exposes ReaperPluginEntry).

helgoboss commented 3 years ago

@Boscop Please see the draft pull request. There are some things that still need to be figured out concerning insert_media().

BTW, if it's urgent, you can always fall back to the low-level API (which brings us down to C level but maybe better than nothing).

helgoboss commented 3 years ago

Btw, can non-VST Reaper plugin DLLs run in the background, or are they only ran on demand like scripts (and need to be compiled as a VST if they should stay running in the background)? I only used the Reaper extension API from inside a VST or from a LUA script until now, so I'm wondering if I should compile this one as a VST or as a normal DLL (that exposes ReaperPluginEntry).

REAPER extension plug-ins are loaded directly at REAPER startup and are unloaded when it quits. So if you want something that's around all the time and doesn't benefit from having multiple instances, an extension plug-in is an even better choice than a VST plug-in. reaper-rs has support for both. No need to write the ReaperPluginEntry stuff yourself, there's a macro for that, see the README.

Boscop commented 3 years ago

Thanks for the quick implementation, I'll try it out asap. Yes, I saw that section in the Readme, which caused me to even consider compiling it as non-VST plugin.

But if I wanted to have a text input field (for entering the path to the (Rust) DLL that will be watched and auto-reloaded, which produces the composition), how can I expose an Editor from a non-VST extension plugin? (And then how to open it inside Reaper?)

Btw, I'm considering using imgui-baseview (or iced_baseview) for the GUI, which GUI lib are you using for your Rust-rewrite of ReaLearn? :)

helgoboss commented 3 years ago

If it's just simple text fields, you could use get_user_inputs(). If you have a child window handle that doesn't need to be a parent, you could dock it by using DockWindowAdd and related functions (not yet in reaper-medium but easy to add). Or how about opening a top-level Boscop webview window 😃, triggered by a REAPER action (which can be assigned to a toolbar button) or a custom entry in the "Extensions" menu?

helgoboss commented 3 years ago

Btw, I'm considering using imgui-baseview (or iced_baseview) for the GUI, which GUI lib are you using for your Rust-rewrite of ReaLearn? :)

I'm using SWELL (which is a part of WDL and exposed via low-level API), but it's not something I would generally recommend because it's raw Win32 UI programming which feels very clunky, more so in a language like Rust. Also, I didn't find a way to avoid having RefCells all over the place because I'm not in control of the event loop (could probably have just ignored reentrancy issues by using UnsafeCell instead, but then it felt like giving up on some of Rust's safety measures). I just did it because SWELL comes as part of REAPER and standard OS UI components are pretty well suited for a tool like ReaLearn.

I'm interested how things work with imgui-baseview and stuff!

jamsoft commented 2 years ago

Having a bit of a nightmare with this at the moment.

I also want to use the InserMedia so I'm down in the low API at the moment.

        unsafe {
            let c_str = CString::new(msg.data).unwrap();
            let c_world: *const c_char = c_str.as_ptr() as *const c_char;
            Reaper::get().medium_reaper().low().InsertMedia(c_world, 1);
        }

I found this solution, which claims to work:

    let c_str = CString::new(to).unwrap();
    let c_world: *const c_char = c_str.as_ptr() as *const c_char;

I was also sure you would have written utility method somewhere for this but I'm just not good enough at reading rust to spot it.

It saying "use of a moved value" on the msg.data in this line let c_str = CString::new(msg.data.).unwrap();

But adding Copy and Clone traits falls foul of the fact it's got two "String" fields ... this is making my OO brain hurt.

I guess firstly, did you create a wrapper / utility method?

If not, how on earth do I get this working? LOL. NURSE!!