From 05576eb9976357bc3870095c0d7e284c655ddb52 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 19 Dec 2013 17:50:01 +0000 Subject: Add support for running plugins from Python by Kaspar Emanuel (fix #939 and #940). git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@5189 a436a847-0d15-0410-975c-d299462d15a1 --- bindings/test/bindings_test_plugin.c | 196 ++++++++++++++++++++++++++++++ bindings/test/bindings_test_plugin.ttl.in | 49 ++++++++ bindings/test/manifest.ttl.in | 7 ++ bindings/test/python/test_api.py | 84 +++++++++++++ bindings/test/python/test_api_mm.py | 70 +++++++++++ 5 files changed, 406 insertions(+) create mode 100644 bindings/test/bindings_test_plugin.c create mode 100644 bindings/test/bindings_test_plugin.ttl.in create mode 100644 bindings/test/manifest.ttl.in create mode 100644 bindings/test/python/test_api.py create mode 100644 bindings/test/python/test_api_mm.py (limited to 'bindings/test') diff --git a/bindings/test/bindings_test_plugin.c b/bindings/test/bindings_test_plugin.c new file mode 100644 index 0000000..86f6fba --- /dev/null +++ b/bindings/test/bindings_test_plugin.c @@ -0,0 +1,196 @@ +/* + Copyright 2006-2011 David Robillard + Copyright 2006 Steve Harris + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** Include standard C headers */ +#include +#include + +/** + LV2 headers are based on the URI of the specification they come from, so a + consistent convention can be used even for unofficial extensions. The URI + of the core LV2 specification is , by + replacing `http:/` with `lv2` any header in the specification bundle can be + included, in this case `lv2.h`. +*/ +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +/** + The URI is the identifier for a plugin, and how the host associates this + implementation in code with its description in data. In this plugin it is + only used once in the code, but defining the plugin URI at the top of the + file is a good convention to follow. If this URI does not match that used + in the data files, the host will fail to load the plugin. +*/ +#define TEST_URI "http://example.org/lilv-bindings-test-plugin" + +/** + In code, ports are referred to by index. An enumeration of port indices + should be defined for readability. +*/ +typedef enum { + TEST_CONTROL_IN = 0, + TEST_CONTROL_OUT = 1, + TEST_AUDIO_IN = 2, + TEST_AUDIO_OUT = 3 +} PortIndex; + +/** + Every plugin defines a private structure for the plugin instance. All data + associated with a plugin instance is stored here, and is available to + every instance method. In this simple plugin, only port buffers need to be + stored, since there is no additional instance data. */ +typedef struct { + // Port buffers +} Test; + +/** + The instantiate() function is called by the host to create a new plugin + instance. The host passes the plugin descriptor, sample rate, and bundle + path for plugins that need to load additional resources (e.g. waveforms). + The features parameter contains host-provided features defined in LV2 + extensions, but this simple plugin does not use any. + + This function is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)malloc(sizeof(Test)); + + return (LV2_Handle)test; +} + +/** + The connect_port() method is called by the host to connect a particular port + to a buffer. The plugin must store the data location, but data may not be + accessed except in run(). + + This method is in the ``audio'' threading class, and is called in the same + context as run(). +*/ +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ +} + +/** + The activate() method is called by the host to initialise and prepare the + plugin instance for running. The plugin must reset all internal state + except for buffer locations set by connect_port(). Since this plugin has + no other internal state, this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +activate(LV2_Handle instance) +{ +} + +/** Process a block of audio (audio thread, must be RT safe). */ +static void +run(LV2_Handle instance, uint32_t n_samples) +{ +} + +/** + The deactivate() method is the counterpart to activate() called by the host + after running the plugin. It indicates that the host will not call run() + again until another call to activate() and is mainly useful for more + advanced plugins with ``live'' characteristics such as those with auxiliary + processing threads. As with activate(), this plugin has no use for this + information so this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +deactivate(LV2_Handle instance) +{ +} + +/** + Destroy a plugin instance (counterpart to instantiate()). + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + The extension_data function returns any extension data supported by the + plugin. Note that this is not an instance method, but a function on the + plugin descriptor. It is usually used by plugins to implement additional + interfaces. This plugin does not have any extension data, so this function + returns NULL. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +/** + Define the LV2_Descriptor for this plugin. It is best to define descriptors + statically to avoid leaking memory and non-portable shared library + constructors and destructors to clean up properly. +*/ +static const LV2_Descriptor descriptor = { + TEST_URI, + instantiate, + connect_port, + activate, + run, + deactivate, + cleanup, + extension_data +}; + +/** + The lv2_descriptor() function is the entry point to the plugin library. The + host will load the library and call this function repeatedly with increasing + indices to find all the plugins defined in the library. The index is not an + indentifier, the URI of the returned descriptor is used to determine the + identify of the plugin. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/bindings/test/bindings_test_plugin.ttl.in b/bindings/test/bindings_test_plugin.ttl.in new file mode 100644 index 0000000..a703432 --- /dev/null +++ b/bindings/test/bindings_test_plugin.ttl.in @@ -0,0 +1,49 @@ +# Lilv Bindings Test Plugin +# Copyright 2011 David Robillard +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix ui: . + + + a lv2:Plugin ; + doap:name "Lilv Bindings Test" ; + doap:license ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 2 ; + lv2:symbol "audio_input" ; + lv2:name "Audio Input" ; + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 3 ; + lv2:symbol "audio_output" ; + lv2:name "Audio Output" ; + ] . diff --git a/bindings/test/manifest.ttl.in b/bindings/test/manifest.ttl.in new file mode 100644 index 0000000..9cc7fa8 --- /dev/null +++ b/bindings/test/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/bindings/test/python/test_api.py b/bindings/test/python/test_api.py new file mode 100644 index 0000000..655abba --- /dev/null +++ b/bindings/test/python/test_api.py @@ -0,0 +1,84 @@ +# Copyright 2013 Kaspar Emanuel +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from lilv import * +import unittest +import os + +class UriTests(unittest.TestCase): + def setUp(self): + self.world = lilv_world_new() + lilv_world_load_all(self.world) + def testInvalidURI(self): + self.uri = lilv_new_uri(self.world, "invalid_uri") + self.assertIsNone(self.uri) + def testInvalidURI2(self): + self.uri = lilv_new_uri(self.world, "invalid_uri") + self.assertFalse( lilv_node_is_uri(self.uri) ) + def testNonExistentURI(self): + self.uri = lilv_new_uri(self.world, "exist:does_not") + plugins = lilv_world_get_all_plugins(self.world) + self.plugin = lilv_plugins_get_by_uri(plugins, self.uri) + self.assertIsNone(self.plugin) + def testPortTypes(self): + self.uri = lilv_new_uri(self.world, LILV_URI_INPUT_PORT) + self.assertIsNotNone(self.uri) + def testPortTypes2(self): + self.uri = lilv_new_uri(self.world, LILV_URI_OUTPUT_PORT) + self.assertIsNotNone(self.uri) + def testPortTypes3(self): + self.uri = lilv_new_uri(self.world, LILV_URI_AUDIO_PORT) + self.assertIsNotNone(self.uri) + def testPortTypes4(self): + self.uri = lilv_new_uri(self.world, LILV_URI_CONTROL_PORT) + self.assertIsNotNone(self.uri) + def tearDown(self): + lilv_node_free(self.uri) + lilv_world_free(self.world) + +class PluginTests(unittest.TestCase): + def setUp(self): + self.world = lilv_world_new() + location = "file://" + os.getcwd() + "/bindings/bindings_test_plugin.lv2/" + self.plugin_uri = lilv_new_uri(self.world, location) + self.assertIsNotNone(self.plugin_uri, "Invalid URI: '" + location + "'") + lilv_world_load_bundle(self.world, self.plugin_uri) + self.plugins = lilv_world_get_all_plugins(self.world) + self.plugin = lilv_plugins_get(self.plugins, lilv_plugins_begin(self.plugins)) + self.assertIsNotNone(self.plugin, msg="Test plugin not found at location: '" + location + "'") + self.assertEqual(location, lilv_node_as_string(lilv_plugin_get_bundle_uri(self.plugin))) + self.instance = lilv_plugin_instantiate(self.plugin, 48000, None) + self.assertIsNotNone(self.instance) + self.lv2_InputPort = lilv_new_uri(self.world, LILV_URI_INPUT_PORT) + self.lv2_OutputPort = lilv_new_uri(self.world, LILV_URI_OUTPUT_PORT) + self.lv2_AudioPort = lilv_new_uri(self.world, LILV_URI_AUDIO_PORT) + self.lv2_ControlPort = lilv_new_uri(self.world, LILV_URI_CONTROL_PORT) + def testPorts(self): + n = lilv_plugin_get_num_ports_of_class(self.plugin, self.lv2_InputPort, self.lv2_AudioPort) + self.assertEqual(n, 1) + def testPorts2(self): + n = lilv_plugin_get_num_ports_of_class(self.plugin, self.lv2_OutputPort, self.lv2_AudioPort) + self.assertEqual(n, 1) + def testPorts3(self): + n = lilv_plugin_get_num_ports_of_class(self.plugin, self.lv2_OutputPort, self.lv2_ControlPort) + self.assertEqual(n, 1) + def testPorts4(self): + n = lilv_plugin_get_num_ports_of_class(self.plugin, self.lv2_InputPort, self.lv2_ControlPort) + self.assertEqual(n, 1) + def tearDown(self): + lilv_node_free(self.lv2_InputPort) + lilv_node_free(self.lv2_OutputPort) + lilv_node_free(self.lv2_AudioPort) + lilv_node_free(self.plugin_uri) + lilv_world_free(self.world) diff --git a/bindings/test/python/test_api_mm.py b/bindings/test/python/test_api_mm.py new file mode 100644 index 0000000..87b34af --- /dev/null +++ b/bindings/test/python/test_api_mm.py @@ -0,0 +1,70 @@ +# Copyright 2013 Kaspar Emanuel +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import lilv +import unittest +import os + +class UriTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.world.load_all(); + def testInvalidURI(self): + self.plugin_uri = self.world.new_uri("invalid_uri") + self.assertEqual(self.plugin_uri, None) + def testInvalidURI2(self): + self.plugin_uri = self.world.new_uri("invalid_uri") + self.assertFalse( lilv.lilv_node_is_uri(self.plugin_uri) ) + def testNonExistentURI(self): + self.plugin_uri = self.world.new_uri("exist:does_not") + self.plugin = self.world.get_all_plugins().get_by_uri(self.plugin_uri) + self.assertEqual(self.plugin, None) + def testPortTypes(self): + self.assertIsNotNone( self.world.new_uri(lilv.LILV_URI_INPUT_PORT) ) + def testPortTypes2(self): + self.assertIsNotNone( self.world.new_uri(lilv.LILV_URI_OUTPUT_PORT) ) + def testPortTypes3(self): + self.assertIsNotNone( self.world.new_uri(lilv.LILV_URI_AUDIO_PORT) ) + def testPortTypes4(self): + self.assertIsNotNone( self.world.new_uri(lilv.LILV_URI_CONTROL_PORT) ) + +class PluginTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + location = "file://" + os.getcwd() + "/bindings/bindings_test_plugin.lv2/" + self.plugin_uri = self.world.new_uri(location) + self.assertIsNotNone(self.plugin_uri, "Invalid URI: '" + location + "'") + self.world.load_bundle(self.plugin_uri) + self.plugins = self.world.get_all_plugins() + self.plugin = self.plugins.get(self.plugins.begin()) + self.assertIsNotNone(self.plugin, msg="Test plugin not found at location: '" + location + "'") + self.assertEqual(location, self.plugin.get_bundle_uri().as_string()) + self.instance = lilv.Instance(self.plugin, 48000, None) + self.assertIsNotNone(self.instance) + self.lv2_InputPort = self.world.new_uri(lilv.LILV_URI_INPUT_PORT) + self.lv2_OutputPort = self.world.new_uri(lilv.LILV_URI_OUTPUT_PORT) + self.lv2_AudioPort = self.world.new_uri(lilv.LILV_URI_AUDIO_PORT) + self.lv2_ControlPort = self.world.new_uri(lilv.LILV_URI_CONTROL_PORT) + def testPorts(self): + n = self.plugin.get_num_ports_of_class(self.lv2_InputPort, self.lv2_AudioPort) + self.assertEqual(n, 1) + def testPorts2(self): + n = self.plugin.get_num_ports_of_class(self.lv2_OutputPort, self.lv2_AudioPort) + self.assertEqual(n, 1) + def testPorts3(self): + n = self.plugin.get_num_ports_of_class(self.lv2_OutputPort, self.lv2_ControlPort) + self.assertEqual(n, 1) + def testPorts4(self): + n = self.plugin.get_num_ports_of_class(self.lv2_InputPort, self.lv2_ControlPort) + self.assertEqual(n, 1) -- cgit v1.2.1