rustaudio / rust-lv2 Goto Github PK
View Code? Open in Web Editor NEWA safe, fast, and modular framework to create LV2 plugins, written in Rust
License: Apache License 2.0
A safe, fast, and modular framework to create LV2 plugins, written in Rust
License: Apache License 2.0
As it stands, macos is unsupported, as stated by an error message that greets anyone who tries to compile on mac. It's kind of odd to me that windows is supported but not macos. I'd like to use the crate but I can't on my macbook as it stands.
The number of samples is not explicitly passed to the rust lv2 run method, while the original method has this information:
unsafe extern "C" fn run(instance: *mut c_void, sample_count: u32)
Is there some way to get the sample count in the plugins run method? I know that if I have an audio port I can get it using the buffer length, but I'm having a somewhat special case. I'm implementing a plugin with just two atom ports (MIDI events) and still need to do frame accurate timing (in this case sequencing). How would I go about that?
Hey there, thanks for the awesome work on lv2 support with Rust!
I just got back on a project I left a couple weeks and it's not compiling anymore:
error[E0432]: unresolved import `proc_macro`
--> /.cargo/registry/src/github.com-1ecc6299db9ec823/urid-derive-0.1.0/src/lib.rs:7:5
|
7 | use proc_macro::TokenStream;
| ^^^^^^^^^^ help: a similar path exists: `syn::proc_macro`
error[E0432]: unresolved import `proc_macro`
--> /.cargo/registry/src/github.com-1ecc6299db9ec823/urid-derive-0.1.0/src/uri_bound.rs:2:5
|
2 | use proc_macro::TokenStream;
| ^^^^^^^^^^ help: a similar path exists: `syn::proc_macro`
error[E0432]: unresolved import `proc_macro`
--> /.cargo/registry/src/github.com-1ecc6299db9ec823/urid-derive-0.1.0/src/urid_collection_derive.rs:1:5
|
1 | use proc_macro::TokenStream;
| ^^^^^^^^^^ help: a similar path exists: `syn::proc_macro`
error: aborting due to 3 previous errors
I'll try to fix it myself by cloning the repo locally, but I thought it was worth reporting as I tried with latest develop
and had the same error.
Cheers
This is a request from Reddit (link to the comment) to create a procedural macro that implements the UriBound
trait for a type and assures that the Null-Terminator is included. Therefore, this would be a nice, safe wrapper for an unsafe trait.
To create a plugin you will need three other structs: One that implements PortContainer
, one that implements FeatureCollection
and one that implements URIDCache
. From my point of view, it is a bit confusing that all of these traits have different suffixes, isn't it?
It would be better if these three traits would have a common suffix, for example PortContainer
, FeatureContainer
and URIDContainer
. However, there already is a FeatureContainer
so this would have to be renamed too. We could also use PortCollection
, FeatureCollection
and URIDCollection
, although I don't quite the sound of these names.
What do you think?
The URI
, UriBound
and URID
systems are very useful, so useful that they could be used by other projects completely separate from the LV2 ecosystem.
Therefore, all URI-related stuff should be bundled in an individual crate; Maybe the lv2-urid
crate, maybe another one.
Trying to build the amp example, but cargo is failing to compile lv2-core-derive (I have never used rust-lv2 before so its a fresh download). I get 2 errors
error[E0432]: unresolved import
syn::export-->path_to/registry/src/github.com-1285ae84e5963aae/lv2-core-derive-2.1.0/src/feature_collection_derive.rs:2:10 | 2 | use syn::export::Span; | ^^^^^^ could not find
exportin
syn`
error[E0432]: unresolved import
syn::export--> path_to/registry/src/github.com-1285ae84e5963aae/lv2-core-derive-2.1.0/src/port_collection_derive.rs:2:10 | 2 | use syn::export::Span; | ^^^^^^ could not find
exportin
syn`
error: aborting due to 2 previous errors For more information about this error, try
rustc --explain E0432. error: could not compile
lv2-core-derive`
A quick search suggests that this issue is not unique to 'rust-lv2':
nushell/nushell#2867
Is there already a Rust binding for the logging functions like lv2_log_error()
?
If so, sorry I couldn't find it, I'm happy to improve the documentation in the places I looked. If not, consider this a feature request.
This is a request from Reddit (link to comment) to change the type of the URI
constant of the UriBound
trait to CStr
or Uri
. Then, the constant would be created with the from_bytes_with_nul_unchecked
function, which explicitly says that the input has to contain a null character and why this trait is unsafe to implement.
This is mostly a cosmetic change, but a good and helpful one.
I recently added the units
crate to the project because the atom
crate needs some of the URIs of that specification. It didn't need much and therefore, I kept it simple and bound the important URIs to marker types.
However, reading through the specification raised the question: How do you properly handle specifications that only introduce RDF structures? Units isn't the only RDF-centric spec; There is also Buf Size, Morph, Options, Parameters, Patch, Port Groups, Port Properties, Presets and Time. All of these specifications have to be handled in one way or another, but how?
To a plugin's code, these specifications aren't that important: They are only used in the configuration files of the plugin and therefore don't appear that much in the code. In special cases, such as units
, it's sufficient to simply bind the URIs to some types and you're done. However, a host needs to know the structure and the relationship of these URIs to parse and understand the configuration files. Therefore, the handling of those specifications needs to contain more information than just the URIs.
From my point of view, we need a RDF representation crate that is capable of working with UriBound
s. UriBound
is quite a nice ecosystem and it would be great if it would also be used for RDF structures. An implementation the Dynamic Manifest specification would need a way to define RDF structures in Rust too. However, there are already a lot of RDF libraries out there; A custom one would be only one among many. One option would be to modify one: We could try to find one that is close enough for our needs and propose the changes needed for the inclusion of UriBound
s. However, this may not end up like we would like it to be or the maintainer of that crate could uncooperative. Then, we would have to create our own crate anyway.
Another nice thing to have would be a procedural macro that can read a Turtle file and emits type and constant definitions equivalent to that file. This could be used to bind the contents of a RDF-centric specification to a crate at build time, which could make plugin discovery faster and less error-prone.
These are just some of my ideas, what do you think about it?
When compiling against the current development branch the MIDI output does not work
This can be for example tested with the plugin Example Fitfth (Rust Edition)
for docs/fifth
How to reproduce:
The above experiment works on b6d02f3 but does not work on 44aac00 (current develop branch)
I did some debugging with rust-gdb and collected the raw buffer of a Note On Midi message in jalv
0x7fe4000b60: 0x00008000 0x00000021 0x00000027 0x00000000
0x7fe4000b70: 0x00008000 0x00000021 0x0000001b 0x00000027
0x7fe4000b80: 0x00000037 0x00000000 0x00000064 0x00000000
0x7fe4000b90: 0x00000003 0x0000000d 0x007f2490
compared to a working message one can see that there are two additional words 0x00008000 0x00000021
compared to a working sequence atom message
0x7fe4008ba0: 0x00008000 0x00000021 0x00000027 0x00000000
0x7fe4008bb0: 0x0000001b 0x00000027 0x00000037 0x00000000
0x7fe4008bc0: 0x00000064 0x00000000 0x00000003 0x0000000d
0x7fe4008bd0: 0x007f3080
This discussion has moved from PR #16. Full discussion as follows:
I know this PR isn't ready for review, but I noticed you pushed b12dd72 on master, and I was wondering what it was useful for until I realized that when implementing the urid URIDCache trait we completely forgot to also bring back the URIDCacheMapping trait implementation! Which is why it is so awkward to use right now. ^^'
Right now the URIDBound ties an URI-bound type to a specific container type, which defeats the most of the point of having an user-customizable URID cache.
The URIDCacheMapping used to work the other way around: it was auto-derived when deriving URIDCache, and it allowed to get the wanted URID with a generic function: my_cache.get_urid::() -> URID, without having to access the fields directly in a way or another, which was very useful for both implementing and using all of the Atom types (which are normally only bound to an URI, not a specific URID cache).
I can make a new PR for this so you can get all the details and explanations, and help you rebase this branch on it later if you'd like. slightly_smiling_face
I generally like the idea of the
URIDBound
since it makes generic methods with URIDs easier to write. For example:fn write_urid<T: URIDBound>(target: &mut u32, cache: &T::CacheType) { *target = T::urid(cache).get(); }This is very handy indeed!
Also, using pre-defined URID caches does not conflict with having user-defined caches, it even supports them! Take a look at the setting for the literal test:
struct German; unsafe impl UriBound for German { const URI: &'static [u8] = b"http://lexvo.org/id/iso639-1/de\0"; } #[derive(URIDCache)] pub struct TestURIDs { atom: AtomURIDCache, german: URID<German>, }Here, we have a case where a URID cache is needed that contains both the URIDs for the atom library and other, custom URIDs. We can simply include that pre-defined cache in our own cache and use it! We don't have to care which URIDs are included or have to update our cache every time a new update for the library comes in.
I guess you thought that specifying a pre-defined URID cache for a URI bound would require a user to always use this specific cache, but as you can see it doesn't! ๐
I generally like the idea of the
URIDBound
since it makes generic methods with URIDs easier to write. For example:fn write_urid<T: URIDBound>(target: &mut u32, cache: &T::CacheType) { *target = T::urid(cache).get(); }This is very handy indeed!
Maybe I missed something, but users would have to use it that way, right? (assuming
test_urids
is an instance ofTestURIDs
here)write_urid::<Float>(&mut buf, &test_urids.atom);โฆ instead of:
write_urid::<Float>(&mut buf, &test_urids);Which would be what the user would have to do in the version that uses
URIDCacheMapping
s. (See my archived experiment).
Implementation would look like this instead:fn write_urid<T: UriBound>(target: &mut u32, cache: &impl URIDCacheMapping<T>) { *target = cache.get_urid().get(); }As you can see, in your version the user has to do the "dispatch" manually still, which can become very cumbersome in more complex scenarios.
An example is Atom Sequences, which need 2 different URIDs on creation: one for the sequence itself, and one for the unit of the event timestamp, so using
URIDBound
would they have to be written like so?:let seq = write_sequence(&mut buffer, &test_urids.cache, &test_urids.unit);
[...]
Also, using pre-defined URID caches does not conflict with having user-defined caches, it even supports them! Take a look at the setting for the literal test:
struct German; unsafe impl UriBound for German { const URI: &'static [u8] = b"http://lexvo.org/id/iso639-1/de\0"; } #[derive(URIDCache)] pub struct TestURIDs { atom: AtomURIDCache, german: URID<German>, }Here, we have a case where a URID cache is needed that contains both the URIDs for the atom library and other, custom URIDs. We can simply include that pre-defined cache in our own cache and use it! We don't have to care which URIDs are included or have to update our cache every time a new update for the library comes in.
I guess you thought that specifying a pre-defined URID cache for a URI bound would require a user to always use this specific cache, but as you can see it doesn't! smiley
I was thinking about trying to do something like this:
#[derive(URIDCache)] pub struct TestURIDs { long: URID<atom::Long>, // A custom cache for a non-custom type german: URID<German>, } write_urid::<Long>(&mut buf, &test_urids); // Doesn't work! The associated cache type is AtomURIDCache write_urid::<German>(&mut buf, &test_urids); // Doesn't work either actually?This is useful in case the plugin author wants only a subset of a spec's provided URID-bound items (which, in case of the
unit
spec for example, is quite a lot), but this is impossible here, because theLong
type is tied to theAtomURIDCache
, so it's the only one that can be passed, and never a custom one.Also, in your example where you add the
German
type, wouldn't it also have to implementURIDBound
, and have a specified cache type to work withwrite_urid
as well?
The State specification has some nice hosts features to allocate temporary files and directories to load and store the state of the plugin. For some reason, I've missed these features when I initially implemented lv2-state
.
The task is to implement the features makePath
, mapPath
, freePath
, loadDefaultState
, and threadSafeRestore
in a sensible way.
Right now, it might not be as important to have a contribution guide or a code of conduct, but it may be when looking into the future. I simply like having things sorted out and right now, we have no pressure yet. Any ideas?
Hello, i'm trying to do some draft for the worker feature and i'm stuck at a early step where i'm trying to determine if the host provide provide the feature. As i understood, the rust-lv2 have already infrastructure to deal with feature, but i can't understand what traits i need to implement and what functions to call in new()
to get the feature.
I tried to implement my own marker feature like 'IsLIve' or 'HardRTCapable' and then, in the new() function, i tried to use features.map.map_type::<MyUriBound>()
but it always return an Urid, even when the Uri of my UriBound doesn't correspond to any spec.
Hello!
I've tried this before, but I think I need to do it again: I would like to resign as a maintainer of Rust-LV2.
It's been a blast working on this project with you and I appreciate what we've done together, but I have to admit that I have to move on. For once, I'm still very busy with my studies, but I'm also active in my student's council now and started to work as a C++ programmer. I just don't have the time to review pull requests and moderate discussions anymore. From my point of view, this is not only a drag for the project, but also potentially dangerous: Since I'm not an active Rust programmer anymore, I may therefore miss a lot errors and opportunities.
I'm glad that you are back @prokopyl and your PR #93 and the new tags are pieces of very good work, and I trust that you will continue do great stuff. May I therefore ask you to take my responsibilities? Since you're already part of the maintainers group, this would only mean that I will transfer the Rust crates to you, and then I will leave the group of maintainers. Is this okay for you?
When I do:
git clone [email protected]:RustAudio/rust-lv2.git
cd rust-lv2
./install_examples.sh
export LV2_PATH=$PWD/target/lv2 # or just add this path to Carla using its own settings
carla
# in Carla, Add Plugin -> Example MIDI Gate (Rust Version)
, Carla crashes after adding the MIDI Gate plugin. I get this output:
Carla 2.4.1 started, status:
Python version: 3.10.0
Qt version: 5.15.2
PyQt version: 5.15.6
Binary dir: /usr/lib64/carla
Resources dir: /usr/share/carla/resources
Frontend pixel ratio is 1.0
libjack.so.0 loaded successfully!
No device set, using 2 inputs and 2 outputs
zsh: IOT instruction (core dumped) carla
The other three examples don't have this issue for me.
initially mentioned in #110, this is the error you get: thread 'main' panicked at 'called Option::unwrap() on a None value', sys/tool/src/main.rs:96:54
I'm unclear the value of the bindgen step -- looks like it generates a file that can be consumed by a rust host? Is it acceptable to add a way to opt out of this?
To my mind, the point of lv2 is to break down language barriers -- it's a standard interface that any language can adhere to, as a plugin or host. Compulsory bindgen seems like prescription of a Rust host.
All the best,
Dan
Is there a way to spawn an own thread? I need to retrieve data from a serial device and somehow send midi messages into the run method. I tried spawning a thread in Plugin::new but it seems like this is forbidden? Using lv2_worker also seems more of a hack for my use case, as i don't want to pass data from run to a worker, but have a worker running all the time.
Why is there a separate derive
crate for macros and why aren't these macros included in the "main" crate?
I mean, it includes very few code and it is only useful together with the "main" crate. Also, having an additional crate introduces additional complexity and we have to reserve yet another crate name.
So, what were your initial reasons to move the macros to a separate crate?
As of now, all port types return &'static
references to their contents, which allows them to be stored outside of the run()
function, at which point the host could invalidate them (the following code compiles fine today):
struct Amp {
foo: Option<&'static [f32]>
}
struct Ports { input: InputPort<Audio> }
impl Plugin for Amp {
fn run(&mut self, ports: &mut Ports, _features: &mut (), _: u32) {
self.foo.replace(&*ports.input); // Uh-oh
}
}
While this example is rather obvious in that it's doing something very odd, issues can be viciously subtle with more complex port types such as Atom
: one could want to store a deserialized value.
I haven't made any in-depth testing, but I see two solutions to this:
The first would be to make PortType
generic over a 'a
lifetime:
pub trait PortType<'a> {
type InputPortType: Sized + 'a;
type OutputPortType: Sized + 'a;
}
impl<'a> PortType<'a> for Audio<'a> {
type InputPortType = &'a [f32];
type OutputPortType = &'a mut [f32];
}
However, this has a cascading effect, which would force all downstream users of port types to specify their lifetimes:
#[derive(PortCollection)]
struct Ports<'a> {
gain: InputPort<'a, Control<'a>>,
input: InputPort<'a, Audio<'a>>,
output: OutputPort<'a, Audio<'a>>,
}
The second option would be to make the associated types themselves generic:
pub trait PortType {
type InputPortType<'a>: Sized;
type OutputPortType<'a>: Sized;
}
impl PortType for Audio {
type InputPortType<'a> = &'a [f32];
type OutputPortType<'a> = &'a mut [f32];
}
However, this would require Generic Associated Types being stabilized, but while it seems to be making steady progress, there doesn't seem to be any deadline coming soon.
Both options are breaking changes however.
I think we could potentially use the first solution now, and move to GATs when they are stabilized, making two breaking changes.
The lv2-sys
crate currently uses bindgen
in it's build script to generate the bindings to the LV2 API headers. The main reason for that is that this always produces bindings that work on your local platform, without the need to maintain a different version of the same crate for every platform.
On the downside, building bindgen
itself and generating the bindings takes a lot of time, about half a minute for even the simplest plugins, and requires clang to be properly installed. Especially the last point raises problems, for example because development setups on Windows are always quite tricky.
Therefore, I request an alternative solution that is faster, doesn't rely on clang, and is easy to maintain. The precise requirements are:
These requirements were developed in the discussion issue #55. Please take a look at it for more context.
Checking midigate v0.1.0 (/home/kerle/Coding/serial2midi-rs/midigate)
error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> midigate/src/lib.rs:80:14
|
80 | .read(self.urids.atom.sequence)
| ^^^^ ------------------------ supplied 1 argument
| |
| expected 2 arguments
|
note: associated function defined here
--> /home/kerle/.cargo/registry/src/github.com-1ecc6299db9ec823/lv2-atom-2.0.0/src/port.rs:50:12
|
50 | pub fn read<'b, A: crate::Atom<'a, 'b>>(
| ^^^^
For more information about this error, try `rustc --explain E0061`.
error: could not compile `midigate` due to previous error
Not really sure why it doesn't compile, I also tried passing () as a second argument as recommended in the read docs but also doesn't compile. Any ideas?
I'm trying to port a somewhat more complex plugin to lv2 that currently requires Cell
at some point, which is not Sync
. If I change the Cell
to something like an atomic type or use RwLock
, it works, but I'm trying to avoid that in the audio path.
Given the lv2 threading rules, which state:
When any function is running for a plugin instance, no other function in the same class may run for that instance.
it seems that Sync may not be necessary?
When the chapters of the books were written, Rust LV2 wasn't able to work in-place and the book described why. With the merger of #93, this isn't true anymore. The book needs to address this and explain that in-place ports should be used.
I used the amp example from the docs and discovered that Ardour 5 to 6.3 do
not support the inPlaceBroken
feature.
After loading Ardour I get this warning and Ardour just ignores the existence
of the plugin.
[WARNING]: Ignoring LV2 plugin "amp" since it cannot do inplace processing.
Tested on Linux amd64 platform. The plugin works with Carla, and carla.lv2 can be
used from within Ardour so it is still kind of usable. But it's a bit sad I would have
to go via Carla.
I understand the rationale from the rust-lv2 user guide for not supporting it, but I would gladly accept some unsafe code in the processing function itself.
Otherwise I would have to look into writing the DSP code in Rust without rust-lv2 and try to use it from a C wrapper library.
So I would suggest three options to deal with this:
InputPort<AudioCopy>
port type - but I am not too deep into the internals of rust-lv2 and the unsafe side of Rust programming.As i mentioned here, my current implementation of the worker spec introduce an unsafty because it allow concurrent access to the plugin in an uncontrolled way. Unfortunately, this safety issue is a global consideration and can't be solved fully in the worker crate, that's why i opened a new issue.
My suggestion:
work
thread can't access to the plugin. This just suppress the problem origin, and this limits what can be done in the work
thread, but this is easy to do and this avoid plugin designers to clutter their code with mutex when they don't have concurrency.work
thread.What i tried/thought:
Other idea(s) for plugin traits variant:
My current idea is to create a PluginBase
gathering all common things between the variant, and use it as supertrait for the 2 plugin variant. Same for PluginInstance.
Does someone have a better idea for plugin variant?
When we do that ? after integrating a first version of the worker in the develop branch ? before ? After having improved some other aspect (like features) ?
I am making this issue to separate the conversation about the implementation in #93, and the following questions that were raised:
There was already a rather large conversation and many points that were stated in the discussion thread in #93 which I won't reproduce here.
Considering this change, if implemented, would not be done until after the next major release (since it would be breaking), discussing it can wait, while restoring compatibility with hosts that do not allow inPlaceBroken
(see #89) is more pressing. ๐
As decided, we still need the worker specification for the upcoming, first release.
This specification introduces a plugin extension and a host feature. Using the worker feature, a plugin can schedule asynchronous, non-real-time work in the run
method, which will be completed before run
is called the next time.
The work itself is done by a work
method introduced by the worker extension. The communication between the run
method and the work
method is done by sending POD, which offers a place to apply the Atom system too.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.