Git Product home page Git Product logo

rust-lv2's People

Contributors

janonard avatar ln42 avatar niclashoyer avatar philipp91 avatar piedoom avatar pieterpenninckx avatar prokopyl avatar suhr avatar yruamalairba avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-lv2's Issues

Add macos support to lv2-sys?

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.

Provide number of samples in plugin run method

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?

Compilation error with rust 1.40

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

Procedural Macro for UriBound

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.

Common derivable trait names

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?

Separate URID crate

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.

Could not compile 'lv2-core-derive'

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 findexportinsyn`

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 findexportinsyn`

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

LV2 log output

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.

How to handle RDF-centric specifications

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 UriBounds. 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 UriBounds. 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?

Midi output broken on develop

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:

  1. jackd -d alsa
  2. jalv http://lv2plug.in/plugins/eg-fifths
  3. jack_midi_dump
  4. jack_midiseq Sequencer 24000 0 60 8000 12000 63 8000
  5. jack_connect Sequencer:out "Example Fifths (Rust Edition):in"
  6. jack_connect "Example Fifths (Rust Edition):out" midi-monitor:input

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 

Generic URID retrieval

This discussion has moved from PR #16. Full discussion as follows:

@prokopyl (original post)

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

@Janonard (original post)

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! ๐Ÿ˜ƒ

@prokopyl (original post)

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 of TestURIDs 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 URIDCacheMappings. (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 the Long type is tied to the AtomURIDCache, 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 implement URIDBound, and have a specified cache type to work with write_urid as well?

Contribution guide and/or Code of Conduct?

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?

How to get host feature ?

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.

Resignation as Maintainer

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?

MIDI Gate Example Crashes Carla

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.

Is bindgen necessary?

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

Spawn an own thread

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 the "derive" crate?

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?

Port types returning &'static references is unsound

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.

Replacement for the `bindgen` build dependency

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:

  1. It is easily maintainable (updating the LV2 headers only requires replacing the headers).
  2. It is reproducible (can be built, tested and deployed by Travis CI, from the command line).
  3. It supports stable, beta, and nightly Rust on all Tier 1 targets.
  4. It is easily extendable to other Tier 2 targets,
  5. Building a plugin with the solution takes an equal or less amount of time.

These requirements were developed in the discussion issue #55. Please take a look at it for more context.

MidiGate example doesn't build

    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?

Does Plugin need to be Sync?

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?

No support for Ardour - No support for inPlaceBroken

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:

  • Don't state in the README that Ardour is supported.
  • Allow plugin developers to have some unsafe'ness.
  • Implement the C side to handle inplace edits of the buffers and
    copy either the input or output values to an extra buffer. The performance hit is better than no support at all. That could maybe be done by providing an InputPort<AudioCopy> port type - but I am not too deep into the internals of rust-lv2 and the unsafe side of Rust programming.

Solve multithreading safety issue.

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:

  • In a first time, i can publish a worker version where the 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.
  • In a second time, we can implement a variant of the plugin trait with threadsafe interface. This may a lot of work because it may involve many code duplication, but this allow plugin designers to do more advanced task in the work thread.

What i tried/thought:

  • To have a variant of the plugin trait, i tried to explore the use of "cargo features" to adapt plugin trait method depending the need. I discovered it's a bad idea, features aren't supposed to be used in this way. When a feature is enabled, it's enabled for the whole project, or worst, for a whole workspace. As consequence, it's impossible to mix variant in a bundle.
  • I quickly looked on the macro_rules side, but i don't see how to use it without making things more difficult.

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) ?

Remove/deprecate inPlaceBroken port types?

I am making this issue to separate the conversation about the implementation in #93, and the following questions that were raised:

  • Should the in-place-compatible port types be the default?
  • Should in-place-broken processing be supported at all?

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. ๐Ÿ™‚

Worker Specification

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.