From 74d7751c37d2c991d244c1c23e1a4cd24451ac41 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 6 Aug 2014 01:26:06 +0000 Subject: Order book chapters in build script rather than by bundle name. --- plugins/eg-amp.lv2/README.txt | 19 + plugins/eg-amp.lv2/amp.c | 229 ++++++++++ plugins/eg-amp.lv2/amp.ttl | 86 ++++ plugins/eg-amp.lv2/manifest.ttl.in | 101 +++++ plugins/eg-amp.lv2/waf | 1 + plugins/eg-amp.lv2/wscript | 67 +++ plugins/eg-fifths.lv2/README.txt | 3 + plugins/eg-fifths.lv2/fifths.c | 196 +++++++++ plugins/eg-fifths.lv2/fifths.ttl | 30 ++ plugins/eg-fifths.lv2/manifest.ttl.in | 8 + plugins/eg-fifths.lv2/uris.h | 64 +++ plugins/eg-fifths.lv2/waf | 1 + plugins/eg-fifths.lv2/wscript | 64 +++ plugins/eg-metro.lv2/README.txt | 9 + plugins/eg-metro.lv2/manifest.ttl.in | 7 + plugins/eg-metro.lv2/metro.c | 352 ++++++++++++++++ plugins/eg-metro.lv2/metro.ttl | 30 ++ plugins/eg-metro.lv2/waf | 1 + plugins/eg-metro.lv2/wscript | 64 +++ plugins/eg-midigate.lv2/README.txt | 10 + plugins/eg-midigate.lv2/manifest.ttl.in | 10 + plugins/eg-midigate.lv2/midigate.c | 227 ++++++++++ plugins/eg-midigate.lv2/midigate.ttl | 56 +++ plugins/eg-midigate.lv2/waf | 1 + plugins/eg-midigate.lv2/wscript | 65 +++ plugins/eg-sampler.lv2/README.txt | 13 + plugins/eg-sampler.lv2/click.wav | Bin 0 -> 644 bytes plugins/eg-sampler.lv2/manifest.ttl.in | 19 + plugins/eg-sampler.lv2/sampler.c | 483 +++++++++++++++++++++ plugins/eg-sampler.lv2/sampler.ttl | 67 +++ plugins/eg-sampler.lv2/sampler_ui.c | 241 +++++++++++ plugins/eg-sampler.lv2/uris.h | 135 ++++++ plugins/eg-sampler.lv2/waf | 1 + plugins/eg-sampler.lv2/wscript | 80 ++++ plugins/eg-scope.lv2/README.txt | 32 ++ plugins/eg-scope.lv2/examploscope.c | 423 +++++++++++++++++++ plugins/eg-scope.lv2/examploscope.ttl.in | 130 ++++++ plugins/eg-scope.lv2/examploscope_ui.c | 655 +++++++++++++++++++++++++++++ plugins/eg-scope.lv2/manifest.ttl.in | 21 + plugins/eg-scope.lv2/uris.h | 73 ++++ plugins/eg-scope.lv2/wscript | 73 ++++ plugins/eg01-amp.lv2/README.txt | 19 - plugins/eg01-amp.lv2/amp.c | 229 ---------- plugins/eg01-amp.lv2/amp.ttl | 86 ---- plugins/eg01-amp.lv2/manifest.ttl.in | 101 ----- plugins/eg01-amp.lv2/waf | 1 - plugins/eg01-amp.lv2/wscript | 67 --- plugins/eg02-midigate.lv2/README.txt | 10 - plugins/eg02-midigate.lv2/manifest.ttl.in | 10 - plugins/eg02-midigate.lv2/midigate.c | 227 ---------- plugins/eg02-midigate.lv2/midigate.ttl | 56 --- plugins/eg02-midigate.lv2/waf | 1 - plugins/eg02-midigate.lv2/wscript | 65 --- plugins/eg03-metro.lv2/README.txt | 9 - plugins/eg03-metro.lv2/manifest.ttl.in | 7 - plugins/eg03-metro.lv2/metro.c | 352 ---------------- plugins/eg03-metro.lv2/metro.ttl | 30 -- plugins/eg03-metro.lv2/waf | 1 - plugins/eg03-metro.lv2/wscript | 64 --- plugins/eg04-sampler.lv2/README.txt | 13 - plugins/eg04-sampler.lv2/click.wav | Bin 644 -> 0 bytes plugins/eg04-sampler.lv2/manifest.ttl.in | 19 - plugins/eg04-sampler.lv2/sampler.c | 483 --------------------- plugins/eg04-sampler.lv2/sampler.ttl | 67 --- plugins/eg04-sampler.lv2/sampler_ui.c | 241 ----------- plugins/eg04-sampler.lv2/uris.h | 135 ------ plugins/eg04-sampler.lv2/waf | 1 - plugins/eg04-sampler.lv2/wscript | 80 ---- plugins/eg05-scope.lv2/README.txt | 32 -- plugins/eg05-scope.lv2/examploscope.c | 423 ------------------- plugins/eg05-scope.lv2/examploscope.ttl.in | 130 ------ plugins/eg05-scope.lv2/examploscope_ui.c | 655 ----------------------------- plugins/eg05-scope.lv2/manifest.ttl.in | 21 - plugins/eg05-scope.lv2/uris.h | 73 ---- plugins/eg05-scope.lv2/wscript | 73 ---- plugins/eg06-fifths.lv2/README.txt | 3 - plugins/eg06-fifths.lv2/fifths.c | 196 --------- plugins/eg06-fifths.lv2/fifths.ttl | 30 -- plugins/eg06-fifths.lv2/manifest.ttl.in | 8 - plugins/eg06-fifths.lv2/uris.h | 64 --- plugins/eg06-fifths.lv2/waf | 1 - plugins/eg06-fifths.lv2/wscript | 64 --- plugins/wscript | 7 +- 83 files changed, 4153 insertions(+), 4148 deletions(-) create mode 100644 plugins/eg-amp.lv2/README.txt create mode 100644 plugins/eg-amp.lv2/amp.c create mode 100644 plugins/eg-amp.lv2/amp.ttl create mode 100644 plugins/eg-amp.lv2/manifest.ttl.in create mode 120000 plugins/eg-amp.lv2/waf create mode 100644 plugins/eg-amp.lv2/wscript create mode 100644 plugins/eg-fifths.lv2/README.txt create mode 100644 plugins/eg-fifths.lv2/fifths.c create mode 100644 plugins/eg-fifths.lv2/fifths.ttl create mode 100644 plugins/eg-fifths.lv2/manifest.ttl.in create mode 100644 plugins/eg-fifths.lv2/uris.h create mode 120000 plugins/eg-fifths.lv2/waf create mode 100644 plugins/eg-fifths.lv2/wscript create mode 100644 plugins/eg-metro.lv2/README.txt create mode 100644 plugins/eg-metro.lv2/manifest.ttl.in create mode 100644 plugins/eg-metro.lv2/metro.c create mode 100644 plugins/eg-metro.lv2/metro.ttl create mode 120000 plugins/eg-metro.lv2/waf create mode 100644 plugins/eg-metro.lv2/wscript create mode 100644 plugins/eg-midigate.lv2/README.txt create mode 100644 plugins/eg-midigate.lv2/manifest.ttl.in create mode 100644 plugins/eg-midigate.lv2/midigate.c create mode 100644 plugins/eg-midigate.lv2/midigate.ttl create mode 120000 plugins/eg-midigate.lv2/waf create mode 100644 plugins/eg-midigate.lv2/wscript create mode 100644 plugins/eg-sampler.lv2/README.txt create mode 100644 plugins/eg-sampler.lv2/click.wav create mode 100644 plugins/eg-sampler.lv2/manifest.ttl.in create mode 100644 plugins/eg-sampler.lv2/sampler.c create mode 100644 plugins/eg-sampler.lv2/sampler.ttl create mode 100644 plugins/eg-sampler.lv2/sampler_ui.c create mode 100644 plugins/eg-sampler.lv2/uris.h create mode 120000 plugins/eg-sampler.lv2/waf create mode 100644 plugins/eg-sampler.lv2/wscript create mode 100644 plugins/eg-scope.lv2/README.txt create mode 100644 plugins/eg-scope.lv2/examploscope.c create mode 100644 plugins/eg-scope.lv2/examploscope.ttl.in create mode 100644 plugins/eg-scope.lv2/examploscope_ui.c create mode 100644 plugins/eg-scope.lv2/manifest.ttl.in create mode 100644 plugins/eg-scope.lv2/uris.h create mode 100644 plugins/eg-scope.lv2/wscript delete mode 100644 plugins/eg01-amp.lv2/README.txt delete mode 100644 plugins/eg01-amp.lv2/amp.c delete mode 100644 plugins/eg01-amp.lv2/amp.ttl delete mode 100644 plugins/eg01-amp.lv2/manifest.ttl.in delete mode 120000 plugins/eg01-amp.lv2/waf delete mode 100644 plugins/eg01-amp.lv2/wscript delete mode 100644 plugins/eg02-midigate.lv2/README.txt delete mode 100644 plugins/eg02-midigate.lv2/manifest.ttl.in delete mode 100644 plugins/eg02-midigate.lv2/midigate.c delete mode 100644 plugins/eg02-midigate.lv2/midigate.ttl delete mode 120000 plugins/eg02-midigate.lv2/waf delete mode 100644 plugins/eg02-midigate.lv2/wscript delete mode 100644 plugins/eg03-metro.lv2/README.txt delete mode 100644 plugins/eg03-metro.lv2/manifest.ttl.in delete mode 100644 plugins/eg03-metro.lv2/metro.c delete mode 100644 plugins/eg03-metro.lv2/metro.ttl delete mode 120000 plugins/eg03-metro.lv2/waf delete mode 100644 plugins/eg03-metro.lv2/wscript delete mode 100644 plugins/eg04-sampler.lv2/README.txt delete mode 100644 plugins/eg04-sampler.lv2/click.wav delete mode 100644 plugins/eg04-sampler.lv2/manifest.ttl.in delete mode 100644 plugins/eg04-sampler.lv2/sampler.c delete mode 100644 plugins/eg04-sampler.lv2/sampler.ttl delete mode 100644 plugins/eg04-sampler.lv2/sampler_ui.c delete mode 100644 plugins/eg04-sampler.lv2/uris.h delete mode 120000 plugins/eg04-sampler.lv2/waf delete mode 100644 plugins/eg04-sampler.lv2/wscript delete mode 100644 plugins/eg05-scope.lv2/README.txt delete mode 100644 plugins/eg05-scope.lv2/examploscope.c delete mode 100644 plugins/eg05-scope.lv2/examploscope.ttl.in delete mode 100644 plugins/eg05-scope.lv2/examploscope_ui.c delete mode 100644 plugins/eg05-scope.lv2/manifest.ttl.in delete mode 100644 plugins/eg05-scope.lv2/uris.h delete mode 100644 plugins/eg05-scope.lv2/wscript delete mode 100644 plugins/eg06-fifths.lv2/README.txt delete mode 100644 plugins/eg06-fifths.lv2/fifths.c delete mode 100644 plugins/eg06-fifths.lv2/fifths.ttl delete mode 100644 plugins/eg06-fifths.lv2/manifest.ttl.in delete mode 100644 plugins/eg06-fifths.lv2/uris.h delete mode 120000 plugins/eg06-fifths.lv2/waf delete mode 100644 plugins/eg06-fifths.lv2/wscript (limited to 'plugins') diff --git a/plugins/eg-amp.lv2/README.txt b/plugins/eg-amp.lv2/README.txt new file mode 100644 index 0000000..41683d3 --- /dev/null +++ b/plugins/eg-amp.lv2/README.txt @@ -0,0 +1,19 @@ +== Simple Amplifier == + +This plugin is a simple example of a basic LV2 plugin with no additional features. +It has audio ports which contain an array of `float`, +and a control port which contains a single `float`. + +LV2 plugins are defined in two parts: code and data. +The code is written in C, or any C compatible language such as C++. +Static data is described separately in the human and machine friendly http://www.w3.org/TeamSubmission/turtle/[Turtle] syntax. + +Generally, the goal is to keep code minimal, +and describe as much as possible in the static data. +There are several advantages to this approach: + + * Hosts can discover and inspect plugins without loading or executing any plugin code. + * Plugin data can be used from a wide range of generic tools like scripting languages and command line utilities. + * The standard data model allows the use of existing vocabularies to describe plugins and related information. + * The language is extensible, so authors may describe any data without requiring changes to the LV2 specification. + * Labels and documentation are translatable, and available to hosts for display in user interfaces. diff --git a/plugins/eg-amp.lv2/amp.c b/plugins/eg-amp.lv2/amp.c new file mode 100644 index 0000000..eea9861 --- /dev/null +++ b/plugins/eg-amp.lv2/amp.c @@ -0,0 +1,229 @@ +/* + 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 AMP_URI "http://lv2plug.in/plugins/eg-amp" + +/** + In code, ports are referred to by index. An enumeration of port indices + should be defined for readability. +*/ +typedef enum { + AMP_GAIN = 0, + AMP_INPUT = 1, + AMP_OUTPUT = 2 +} 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 + const float* gain; + const float* input; + float* output; +} Amp; + +/** + 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) +{ + Amp* amp = (Amp*)malloc(sizeof(Amp)); + + return (LV2_Handle)amp; +} + +/** + 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) +{ + Amp* amp = (Amp*)instance; + + switch ((PortIndex)port) { + case AMP_GAIN: + amp->gain = (const float*)data; + break; + case AMP_INPUT: + amp->input = (const float*)data; + break; + case AMP_OUTPUT: + amp->output = (float*)data; + break; + } +} + +/** + 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) +{ +} + +/** Define a macro for converting a gain in dB to a coefficient. */ +#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) + +/** + The `run()` method is the main process function of the plugin. It processes + a block of audio in the audio context. Since this plugin is + `lv2:hardRTCapable`, `run()` must be real-time safe, so blocking (e.g. with + a mutex) or memory allocation are not allowed. +*/ +static void +run(LV2_Handle instance, uint32_t n_samples) +{ + const Amp* amp = (const Amp*)instance; + + const float gain = *(amp->gain); + const float* const input = amp->input; + float* const output = amp->output; + + const float coef = DB_CO(gain); + + for (uint32_t pos = 0; pos < n_samples; pos++) { + output[pos] = input[pos] * coef; + } +} + +/** + The `deactivate()` method is the counterpart to `activate()`, and is 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; +} + +/** + Every plugin must define an `LV2_Descriptor`. 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 = { + AMP_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/plugins/eg-amp.lv2/amp.ttl b/plugins/eg-amp.lv2/amp.ttl new file mode 100644 index 0000000..f4a87f2 --- /dev/null +++ b/plugins/eg-amp.lv2/amp.ttl @@ -0,0 +1,86 @@ +# The full description of the plugin is in this file, which is linked to from +# `manifest.ttl`. This is done so the host only needs to scan the relatively +# small `manifest.ttl` files to quickly discover all plugins. + +@prefix doap: . +@prefix lv2: . +@prefix rdf: . +@prefix rdfs: . + +# First the type of the plugin is described. All plugins must explicitly list +# `lv2:Plugin` as a type. A more specific type should also be given, where +# applicable, so hosts can present a nicer UI for loading plugins. Note that +# this URI is the identifier of the plugin, so if it does not match the one in +# `manifest.ttl`, the host will not discover the plugin data at all. + + a lv2:Plugin , + lv2:AmplifierPlugin ; +# Plugins are associated with a project, where common information like +# developers, home page, and so on are described. This plugin is part of the +# LV2 project, which has URI , and is described +# elsewhere. Typical plugin collections will describe the project in +# manifest.ttl + lv2:project ; +# Every plugin must have a name, described with the doap:name property. +# Translations to various languages can be added by putting a language tag +# after strings as shown. + doap:name "Simple Amplifier" , + "简单放大器"@ch , + "Einfacher Verstärker"@de , + "Simple Amp"@en-gb , + "Amplificador Simple"@es , + "Amplificateur de Base"@fr , + "Amplificatore Semplice"@it , + "簡単なアンプ"@jp , + "Просто Усилитель"@ru ; + doap:license ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ +# Every port must have at least two types, one that specifies direction +# (lv2:InputPort or lv2:OutputPort), and another to describe the data type. +# This port is a lv2:ControlPort, which means it contains a single float. + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "gain" ; + lv2:name "Gain" , + "收益"@ch , + "Gewinn"@de , + "Gain"@en-gb , + "Aumento"@es , + "Gain"@fr , + "Guadagno"@it , + "利益"@jp , + "Увеличение"@ru ; +# An lv2:ControlPort should always describe its default value, and usually a +# minimum and maximum value. Defining a range is not strictly required, but +# should be done wherever possible to aid host support, particularly for UIs. + lv2:default 0.0 ; + lv2:minimum -90.0 ; + lv2:maximum 24.0 ; + lv2:scalePoint [ + rdfs:label "+5" ; + rdf:value 5.0 + ] , [ + rdfs:label "0" ; + rdf:value 0.0 + ] , [ + rdfs:label "-5" ; + rdf:value -5.0 + ] , [ + rdfs:label "-10" ; + rdf:value -10.0 + ] + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 1 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . diff --git a/plugins/eg-amp.lv2/manifest.ttl.in b/plugins/eg-amp.lv2/manifest.ttl.in new file mode 100644 index 0000000..da8a2c4 --- /dev/null +++ b/plugins/eg-amp.lv2/manifest.ttl.in @@ -0,0 +1,101 @@ +# ==== Bundles ==== +# +# LV2 plugins are installed in ``bundles'', a directory with a particular +# format. Inside the bundle, the entry point is a file called `manifest.ttl`. +# The manifest lists the plugins (or other resources) that are in this bundle, +# and the files that contain further information. +# +# Hosts typically read the `manifest.ttl` of every bundle when starting up to +# discover what LV2 plugins and other resources are present. Accordingly, +# manifest files should be as small as possible for performance reasons. +# +# +# ==== Namespace Prefixes ==== +# +# Turtle files contain many URIs. To make this more readable, prefixes can be +# defined. For example, with the `lv2:` prefix below, instead of +# the shorter form `lv2:Plugin` can be +# used. This is just a shorthand for URIs within one file, the prefixes are +# not significant otherwise. + +@prefix lv2: . +@prefix rdfs: . + +# ==== A Plugin Entry ==== + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + +# The token `@LIB_EXT@` above is replaced by the build system with the +# appropriate extension for the current platform (e.g. .so, .dylib, .dll). +# This file is called called `manifest.ttl.in` rather than `manifest.ttl` +# to indicate that it is not the final file to be installed. +# This is not necessary, but is a good idea for portable plugins. +# For reability, the following text will assume `.so` is the extension used. +# +# In short, this declares that the resource with URI +# `http://lv2plug.in/plugins/eg-amp` is an LV2 plugin, with executable code in +# the file `amp.so` and a full description in `amp.ttl`. These paths are +# relative to the bundle directory. +# +# There are 3 statements in this description: +# [options="header"] +# |================================================================ +# | Subject | Predicate | Object +# | | a | lv2:Plugin +# | | lv2:binary | +# | | rdfs:seeAlso | +# |================================================================ +# +# The semicolon is used to continue the previous subject; an equivalent +# but more verbose syntax for the same data is: + + a lv2:Plugin . + lv2:binary . + rdfs:seeAlso . + +# (Since this data is equivalent, it is safe, if pointless, to list it twice) +# +# The documentation for a URI can often be found by visiting that URI in a web +# browser, e.g. the documentation for lv2:binary can be found at +# . All standard LV2 classes and +# properties are documented in this way, so if you encounter a URI in some data +# which you do not understand, try this first. +# +# The URI of a plugin does not need to be a resolvable web address, it just +# serves as a global identifier. However, it is a good idea to use an actual +# web address if possible for easy access documentation, downloads, and so on, +# even if no documents are currently hosted there. There are compatibility +# rules about when the URI of a plugin must be changed, see the +# http://lv2plug.in/ns/lv2core[LV2 specification] for details. Note that this +# does not require authors to control a top-level domain; for example, URIs in +# project directories at shared hosting sites are fine. It is not required to +# use HTTP URIs, but use of other schemes is strongly discouraged. +# +# AUTHORS MUST NOT CREATE URIS AT DOMAINS THEY DO NOT CONTROL WITHOUT +# PERMISSION, AND *ESPECIALLY* MUST NOT CREATE INVALID URIS, E.G. WHERE THE +# PORTION FOLLOWING ``http://'' IS NOT A DOMAIN NAME. If you need an example +# URI, the domain http://example.org/ is reserved for this purpose. +# +# A detailed explanation of each statement follows. + + a lv2:Plugin . + +# The `a`, as in ``is a'', is a Turtle shortcut for `rdf:type`. +# `lv2:Plugin` expands to (using the +# `lv2:` prefix above) which is the type of all LV2 plugins. +# This statement means `` is an LV2 plugin''. + + lv2:binary . + +# This says ```eg-amp` has executable code in the file `amp.so`''. +# Relative URIs in manifest files are relative to the bundle directory, so this +# refers to the file amp.so in the same directory as this manifest.ttl file. + + rdfs:seeAlso . + +# This says ``there is more information about `eg-amp` in the file `amp.ttl`''. +# The host will look at all such files when it needs to actually use or +# investigate the plugin. diff --git a/plugins/eg-amp.lv2/waf b/plugins/eg-amp.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-amp.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg-amp.lv2/wscript b/plugins/eg-amp.lv2/wscript new file mode 100644 index 0000000..d4295ff --- /dev/null +++ b/plugins/eg-amp.lv2/wscript @@ -0,0 +1,67 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-amp.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Amp Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') + + conf.check(features='c cprogram', lib='m', uselib_store='M', mandatory=False) + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-amp.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-amp.lv2) + for i in ['amp.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = None + if autowaf.is_child: + includes = '../..' + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'amp.c', + name = 'amp', + target = '%s/amp' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'M LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg-fifths.lv2/README.txt b/plugins/eg-fifths.lv2/README.txt new file mode 100644 index 0000000..2154321 --- /dev/null +++ b/plugins/eg-fifths.lv2/README.txt @@ -0,0 +1,3 @@ +== Fifths == + +This plugin demonstrates simple MIDI event reading and writing. diff --git a/plugins/eg-fifths.lv2/fifths.c b/plugins/eg-fifths.lv2/fifths.c new file mode 100644 index 0000000..c7d12e1 --- /dev/null +++ b/plugins/eg-fifths.lv2/fifths.c @@ -0,0 +1,196 @@ +/* + LV2 Fifths Example Plugin + Copyright 2014 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. +*/ + +#include +#include +#include +#ifndef __cplusplus +# include +#endif + +#include + +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "./uris.h" + +enum { + FIFTHS_IN = 0, + FIFTHS_OUT = 1 +}; + +typedef struct { + // Features + LV2_URID_Map* map; + + // Ports + const LV2_Atom_Sequence* in_port; + LV2_Atom_Sequence* out_port; + + // URIs + FifthsURIs uris; +} Fifths; + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Fifths* self = (Fifths*)instance; + switch (port) { + case FIFTHS_IN: + self->in_port = (const LV2_Atom_Sequence*)data; + break; + case FIFTHS_OUT: + self->out_port = (LV2_Atom_Sequence*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + // Allocate and initialise instance structure. + Fifths* self = (Fifths*)malloc(sizeof(Fifths)); + if (!self) { + return NULL; + } + memset(self, 0, sizeof(Fifths)); + + // Get host features + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + self->map = (LV2_URID_Map*)features[i]->data; + } + } + if (!self->map) { + fprintf(stderr, "Missing feature urid:map\n"); + free(self); + return NULL; + } + + // Map URIs and initialise forge/logger + map_fifths_uris(self->map, &self->uris); + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +static void +run(LV2_Handle instance, + uint32_t sample_count) +{ + Fifths* self = (Fifths*)instance; + FifthsURIs* uris = &self->uris; + + // Struct for a 3 byte MIDI event, used for writing notes + typedef struct { + LV2_Atom_Event event; + uint8_t msg[3]; + } MIDINoteEvent; + + // Initially self->out_port contains a Chunk with size set to capacity + + // Get the capacity + const uint32_t out_capacity = self->out_port->atom.size; + + // Write an empty Sequence header to the output + lv2_atom_sequence_clear(self->out_port); + self->out_port->atom.type = self->in_port->atom.type; + + // Read incoming events + LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) { + if (ev->body.type == uris->midi_Event) { + const uint8_t* const msg = (const uint8_t*)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + case LV2_MIDI_MSG_NOTE_OFF: + // Forward note to output + lv2_atom_sequence_append_event( + self->out_port, out_capacity, ev); + + const uint8_t note = msg[1]; + if (note <= 127 - 7) { + // Make a note one 5th (7 semitones) higher than input + MIDINoteEvent fifth; + + // Could simply do fifth.event = *ev here instead... + fifth.event.time.frames = ev->time.frames; // Same time + fifth.event.body.type = ev->body.type; // Same type + fifth.event.body.size = ev->body.size; // Same size + + fifth.msg[0] = msg[0]; // Same status + fifth.msg[1] = msg[1] + 7; // Pitch up 7 semitones + fifth.msg[2] = msg[2]; // Same velocity + + // Write 5th event + lv2_atom_sequence_append_event( + self->out_port, out_capacity, &fifth.event); + } + break; + default: + // Forward all other MIDI events directly + lv2_atom_sequence_append_event( + self->out_port, out_capacity, ev); + break; + } + } + } +} + +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +static const LV2_Descriptor descriptor = { + EG_FIFTHS_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-fifths.lv2/fifths.ttl b/plugins/eg-fifths.lv2/fifths.ttl new file mode 100644 index 0000000..7f58a33 --- /dev/null +++ b/plugins/eg-fifths.lv2/fifths.ttl @@ -0,0 +1,30 @@ +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix urid: . +@prefix midi: . + + + a lv2:Plugin ; + doap:name "Example Fifths" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + lv2:index 0 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + lv2:index 1 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . diff --git a/plugins/eg-fifths.lv2/manifest.ttl.in b/plugins/eg-fifths.lv2/manifest.ttl.in new file mode 100644 index 0000000..f87f2c1 --- /dev/null +++ b/plugins/eg-fifths.lv2/manifest.ttl.in @@ -0,0 +1,8 @@ +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/plugins/eg-fifths.lv2/uris.h b/plugins/eg-fifths.lv2/uris.h new file mode 100644 index 0000000..e174fb4 --- /dev/null +++ b/plugins/eg-fifths.lv2/uris.h @@ -0,0 +1,64 @@ +/* + LV2 Fifths Example Plugin + Copyright 2014 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. +*/ + +#ifndef FIFTHS_URIS_H +#define FIFTHS_URIS_H + +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" + +#define EG_FIFTHS_URI "http://lv2plug.in/plugins/eg-fifths" +#define EG_FIFTHS__sample EG_FIFTHS_URI "#sample" +#define EG_FIFTHS__applySample EG_FIFTHS_URI "#applySample" +#define EG_FIFTHS__freeSample EG_FIFTHS_URI "#freeSample" + +typedef struct { + LV2_URID atom_Blank; + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + LV2_URID eg_applySample; + LV2_URID eg_sample; + LV2_URID eg_freeSample; + LV2_URID midi_Event; + LV2_URID patch_Set; + LV2_URID patch_property; + LV2_URID patch_value; +} FifthsURIs; + +static inline void +map_fifths_uris(LV2_URID_Map* map, FifthsURIs* uris) +{ + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); + uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); + uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); + uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); + uris->eg_applySample = map->map(map->handle, EG_FIFTHS__applySample); + uris->eg_freeSample = map->map(map->handle, EG_FIFTHS__freeSample); + uris->eg_sample = map->map(map->handle, EG_FIFTHS__sample); + uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); + uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris->patch_property = map->map(map->handle, LV2_PATCH__property); + uris->patch_value = map->map(map->handle, LV2_PATCH__value); +} + +#endif /* FIFTHS_URIS_H */ diff --git a/plugins/eg-fifths.lv2/waf b/plugins/eg-fifths.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-fifths.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg-fifths.lv2/wscript b/plugins/eg-fifths.lv2/wscript new file mode 100644 index 0000000..46e2345 --- /dev/null +++ b/plugins/eg-fifths.lv2/wscript @@ -0,0 +1,64 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-fifths.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Fifths Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-fifths.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-fifths.lv2) + for i in ['fifths.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'fifths.c', + name = 'fifths', + target = '%s/fifths' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'SNDFILE LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg-metro.lv2/README.txt b/plugins/eg-metro.lv2/README.txt new file mode 100644 index 0000000..5e9a84a --- /dev/null +++ b/plugins/eg-metro.lv2/README.txt @@ -0,0 +1,9 @@ +== Metronome == + +This plugin demonstrates tempo synchronisation by clicking on every beat. The +host sends this information to the plugin as events, so an event with new time +and tempo information will be received whenever there is a change. + +Time is assumed to continue rolling at the tempo and speed defined by the last +received tempo event, even across cycles, until a new tempo event is received +or the plugin is deactivated. diff --git a/plugins/eg-metro.lv2/manifest.ttl.in b/plugins/eg-metro.lv2/manifest.ttl.in new file mode 100644 index 0000000..bd93f66 --- /dev/null +++ b/plugins/eg-metro.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/plugins/eg-metro.lv2/metro.c b/plugins/eg-metro.lv2/metro.c new file mode 100644 index 0000000..5e484b7 --- /dev/null +++ b/plugins/eg-metro.lv2/metro.c @@ -0,0 +1,352 @@ +/* + LV2 Metronome Example Plugin + Copyright 2012 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. +*/ + +#include +#include +#include +#include +#ifndef __cplusplus +# include +#endif + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/time/time.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#ifndef M_PI +# define M_PI 3.14159265 +#endif + +#define EG_METRO_URI "http://lv2plug.in/plugins/eg-metro" + +typedef struct { + LV2_URID atom_Blank; + LV2_URID atom_Float; + LV2_URID atom_Object; + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID time_Position; + LV2_URID time_barBeat; + LV2_URID time_beatsPerMinute; + LV2_URID time_speed; +} MetroURIs; + +static const double attack_s = 0.005; +static const double decay_s = 0.075; + +enum { + METRO_CONTROL = 0, + METRO_OUT = 1 +}; + +/** During execution this plugin can be in one of 3 states: */ +typedef enum { + STATE_ATTACK, // Envelope rising + STATE_DECAY, // Envelope lowering + STATE_OFF // Silent +} State; + +/** + This plugin must keep track of more state than previous examples to be able + to render audio. The basic idea is to generate a single cycle of a sine + wave which is conceptually played continuously. The 'tick' is generated by + enveloping the amplitude so there is a short attack/decay peak around a + tick, and silence the rest of the time. + + This example uses a simple AD envelope with fixed parameters. A more + sophisticated implementation might use a more advanced envelope and allow + the user to modify these parameters, the frequency of the wave, and so on. + */ +typedef struct { + LV2_URID_Map* map; // URID map feature + MetroURIs uris; // Cache of mapped URIDs + + struct { + LV2_Atom_Sequence* control; + float* output; + } ports; + + // Variables to keep track of the tempo information sent by the host + double rate; // Sample rate + float bpm; // Beats per minute (tempo) + float speed; // Transport speed (usually 0=stop, 1=play) + + uint32_t elapsed_len; // Frames since the start of the last click + uint32_t wave_offset; // Current play offset in the wave + State state; // Current play state + + // One cycle of a sine wave + float* wave; + uint32_t wave_len; + + // Envelope parameters + uint32_t attack_len; + uint32_t decay_len; +} Metro; + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Metro* self = (Metro*)instance; + + switch (port) { + case METRO_CONTROL: + self->ports.control = (LV2_Atom_Sequence*)data; + break; + case METRO_OUT: + self->ports.output = (float*)data; + break; + default: + break; + } +} + +/** + The activate() method resets the state completely, so the wave offset is + zero and the envelope is off. +*/ +static void +activate(LV2_Handle instance) +{ + Metro* self = (Metro*)instance; + + self->elapsed_len = 0; + self->wave_offset = 0; + self->state = STATE_OFF; +} + +/** + This plugin does a bit more work in instantiate() than the previous + examples. The tempo updates from the host contain several URIs, so those + are mapped, and the sine wave to be played needs to be generated based on + the current sample rate. +*/ +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Metro* self = (Metro*)calloc(1, sizeof(Metro)); + if (!self) { + return NULL; + } + + // Scan host features for URID map + LV2_URID_Map* map = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + map = (LV2_URID_Map*)features[i]->data; + } + } + if (!map) { + fprintf(stderr, "Host does not support urid:map.\n"); + free(self); + return NULL; + } + + // Map URIS + MetroURIs* const uris = &self->uris; + self->map = map; + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); + uris->atom_Object = map->map(map->handle, LV2_ATOM__Object); + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); + uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); + uris->time_Position = map->map(map->handle, LV2_TIME__Position); + uris->time_barBeat = map->map(map->handle, LV2_TIME__barBeat); + uris->time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute); + uris->time_speed = map->map(map->handle, LV2_TIME__speed); + + // Initialise instance fields + self->rate = rate; + self->bpm = 120.0f; + self->attack_len = (uint32_t)(attack_s * rate); + self->decay_len = (uint32_t)(decay_s * rate); + self->state = STATE_OFF; + + // Generate one cycle of a sine wave at the desired frequency + const double freq = 440.0 * 2.0; + const double amp = 0.5; + self->wave_len = (uint32_t)(rate / freq); + self->wave = (float*)malloc(self->wave_len * sizeof(float)); + for (uint32_t i = 0; i < self->wave_len; ++i) { + self->wave[i] = (float)(sin(i * 2 * M_PI * freq / rate) * amp); + } + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + Play back audio for the range [begin..end) relative to this cycle. This is + called by run() in-between events to output audio up until the current time. +*/ +static void +play(Metro* self, uint32_t begin, uint32_t end) +{ + float* const output = self->ports.output; + const uint32_t frames_per_beat = 60.0f / self->bpm * self->rate; + + if (self->speed == 0.0f) { + memset(output, 0, (end - begin) * sizeof(float)); + return; + } + + for (uint32_t i = begin; i < end; ++i) { + switch (self->state) { + case STATE_ATTACK: + // Amplitude increases from 0..1 until attack_len + output[i] = self->wave[self->wave_offset] * + self->elapsed_len / (float)self->attack_len; + if (self->elapsed_len >= self->attack_len) { + self->state = STATE_DECAY; + } + break; + case STATE_DECAY: + // Amplitude decreases from 1..0 until attack_len + decay_len + output[i] = 0.0f; + output[i] = self->wave[self->wave_offset] * + (1 - ((self->elapsed_len - self->attack_len) / + (float)self->decay_len)); + if (self->elapsed_len >= self->attack_len + self->decay_len) { + self->state = STATE_OFF; + } + break; + case STATE_OFF: + output[i] = 0.0f; + } + + // We continuously play the sine wave regardless of envelope + self->wave_offset = (self->wave_offset + 1) % self->wave_len; + + // Update elapsed time and start attack if necessary + if (++self->elapsed_len == frames_per_beat) { + self->state = STATE_ATTACK; + self->elapsed_len = 0; + } + } +} + +/** + Update the current position based on a host message. This is called by + run() when a time:Position is received. +*/ +static void +update_position(Metro* self, const LV2_Atom_Object* obj) +{ + const MetroURIs* uris = &self->uris; + + // Received new transport position/speed + LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; + lv2_atom_object_get(obj, + uris->time_barBeat, &beat, + uris->time_beatsPerMinute, &bpm, + uris->time_speed, &speed, + NULL); + if (bpm && bpm->type == uris->atom_Float) { + // Tempo changed, update BPM + self->bpm = ((LV2_Atom_Float*)bpm)->body; + } + if (speed && speed->type == uris->atom_Float) { + // Speed changed, e.g. 0 (stop) to 1 (play) + self->speed = ((LV2_Atom_Float*)speed)->body; + } + if (beat && beat->type == uris->atom_Float) { + // Received a beat position, synchronise + // This hard sync may cause clicks, a real plugin would be more graceful + const float frames_per_beat = 60.0f / self->bpm * self->rate; + const float bar_beats = ((LV2_Atom_Float*)beat)->body; + const float beat_beats = bar_beats - floorf(bar_beats); + self->elapsed_len = beat_beats * frames_per_beat; + if (self->elapsed_len < self->attack_len) { + self->state = STATE_ATTACK; + } else if (self->elapsed_len < self->attack_len + self->decay_len) { + self->state = STATE_DECAY; + } else { + self->state = STATE_OFF; + } + } +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Metro* self = (Metro*)instance; + const MetroURIs* uris = &self->uris; + + // Work forwards in time frame by frame, handling events as we go + const LV2_Atom_Sequence* in = self->ports.control; + uint32_t last_t = 0; + for (const LV2_Atom_Event* ev = lv2_atom_sequence_begin(&in->body); + !lv2_atom_sequence_is_end(&in->body, in->atom.size, ev); + ev = lv2_atom_sequence_next(ev)) { + + // Play the click for the time slice from last_t until now + play(self, last_t, ev->time.frames); + + // Check if this event is an Object + // (or deprecated Blank to tolerate old hosts) + if (ev->body.type == uris->atom_Object || + ev->body.type == uris->atom_Blank) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; + if (obj->body.otype == uris->time_Position) { + // Received position information, update + update_position(self, obj); + } + } + + // Update time for next iteration and move to next event + last_t = ev->time.frames; + } + + // Play for remainder of cycle + play(self, last_t, sample_count); +} + +static const LV2_Descriptor descriptor = { + EG_METRO_URI, + instantiate, + connect_port, + activate, + run, + NULL, // deactivate, + cleanup, + NULL, // extension_data +}; + +LV2_SYMBOL_EXPORT const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-metro.lv2/metro.ttl b/plugins/eg-metro.lv2/metro.ttl new file mode 100644 index 0000000..8b4af3d --- /dev/null +++ b/plugins/eg-metro.lv2/metro.ttl @@ -0,0 +1,30 @@ +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix time: . +@prefix urid: . + + + a lv2:Plugin ; + doap:name "Example Metronome" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; +# Since this port supports time:Position, the host knows to deliver time and +# tempo information + atom:supports time:Position ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" ; + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 1 ; + lv2:symbol "out" ; + lv2:name "Out" ; + ] . diff --git a/plugins/eg-metro.lv2/waf b/plugins/eg-metro.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-metro.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg-metro.lv2/wscript b/plugins/eg-metro.lv2/wscript new file mode 100644 index 0000000..40642b6 --- /dev/null +++ b/plugins/eg-metro.lv2/wscript @@ -0,0 +1,64 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-metro.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Metro Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='0.2.0', uselib_store='LV2') + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-metro.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-metro.lv2) + bld(features = 'subst', + is_copy = True, + source = 'metro.ttl', + target = '%s/metro.ttl' % bundle, + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'metro.c', + name = 'metro', + target = '%s/metro' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg-midigate.lv2/README.txt b/plugins/eg-midigate.lv2/README.txt new file mode 100644 index 0000000..8f4a0f0 --- /dev/null +++ b/plugins/eg-midigate.lv2/README.txt @@ -0,0 +1,10 @@ +== MIDI Gate == + +This plugin demonstrates: + + * Receiving MIDI input + + * Processing audio based on MIDI events with sample accuracy + + * Supporting MIDI programs which the host can control/automate, or present a + user interface for with human readable labels diff --git a/plugins/eg-midigate.lv2/manifest.ttl.in b/plugins/eg-midigate.lv2/manifest.ttl.in new file mode 100644 index 0000000..d32f1dc --- /dev/null +++ b/plugins/eg-midigate.lv2/manifest.ttl.in @@ -0,0 +1,10 @@ +# The manifest.ttl file follows the same template as the previous example. + +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/plugins/eg-midigate.lv2/midigate.c b/plugins/eg-midigate.lv2/midigate.c new file mode 100644 index 0000000..8d2bd74 --- /dev/null +++ b/plugins/eg-midigate.lv2/midigate.c @@ -0,0 +1,227 @@ +/* + Copyright 2013 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. +*/ + +#include + +#include +#include + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" + +typedef enum { + MIDIGATE_CONTROL = 0, + MIDIGATE_IN = 1, + MIDIGATE_OUT = 2 +} PortIndex; + +typedef struct { + // Port buffers + const LV2_Atom_Sequence* control; + const float* in; + float* out; + + // Features + LV2_URID_Map* map; + + struct { + LV2_URID midi_MidiEvent; + } uris; + + unsigned n_active_notes; + unsigned program; // 0 = normal, 1 = inverted +} Midigate; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + /** Scan features array for the URID feature we need. */ + LV2_URID_Map* map = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + map = (LV2_URID_Map*)features[i]->data; + break; + } + } + if (!map) { + /** + No URID feature given. This is a host bug since we require this + feature, but should be handled gracefully anyway. + */ + return NULL; + } + + Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); + self->map = map; + self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); + + return (LV2_Handle)self; +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Midigate* self = (Midigate*)instance; + + switch ((PortIndex)port) { + case MIDIGATE_CONTROL: + self->control = (const LV2_Atom_Sequence*)data; + break; + case MIDIGATE_IN: + self->in = (const float*)data; + break; + case MIDIGATE_OUT: + self->out = (float*)data; + break; + } +} + +static void +activate(LV2_Handle instance) +{ + Midigate* self = (Midigate*)instance; + self->n_active_notes = 0; + self->program = 0; +} + +/** + A function to write a chunk of output, to be called from run(). If the gate + is high, then the input will be passed through for this chunk, otherwise + silence is written. +*/ +static void +write_output(Midigate* self, uint32_t offset, uint32_t len) +{ + const bool active = (self->program == 0) + ? (self->n_active_notes > 0) + : (self->n_active_notes == 0); + if (active) { + memcpy(self->out + offset, self->in + offset, len * sizeof(float)); + } else { + memset(self->out + offset, 0, len * sizeof(float)); + } +} + +/** + This plugin works through the cycle in chunks starting at offset zero. The + +offset+ represents the current time within this this cycle, so + the output from 0 to +offset+ has already been written. + + MIDI events are read in a loop. In each iteration, the number of active + notes (on note on and note off) or the program (on program change) is + updated, then the output is written up until the current event time. Then + +offset+ is updated and the next event is processed. After the loop the + final chunk from the last event to the end of the cycle is emitted. + + There is currently no standard way to describe MIDI programs in LV2, so the + host has no way of knowing that these programs exist and should be presented + to the user. A future version of LV2 will address this shortcoming. + + This pattern of iterating over input events and writing output along the way + is a common idiom for writing sample accurate output based on event input. + + Note that this simple example simply writes input or zero for each sample + based on the gate. A serious implementation would need to envelope the + transition to avoid aliasing. +*/ +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Midigate* self = (Midigate*)instance; + uint32_t offset = 0; + + LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) { + if (ev->body.type == self->uris.midi_MidiEvent) { + const uint8_t* const msg = (const uint8_t*)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + ++self->n_active_notes; + break; + case LV2_MIDI_MSG_NOTE_OFF: + --self->n_active_notes; + break; + case LV2_MIDI_MSG_PGM_CHANGE: + if (msg[1] == 0 || msg[1] == 1) { + self->program = msg[1]; + } + break; + default: break; + } + } + + write_output(self, offset, ev->time.frames - offset); + offset = (uint32_t)ev->time.frames; + } + + write_output(self, offset, sample_count - offset); +} + +/** + We have no resources to free on deactivation. + Note that the next call to activate will re-initialise the state, namely + self->n_active_notes, so there is no need to do so here. +*/ +static void +deactivate(LV2_Handle instance) +{} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + This plugin also has no extension data to return. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +static const LV2_Descriptor descriptor = { + MIDIGATE_URI, + instantiate, + connect_port, + activate, + run, + deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-midigate.lv2/midigate.ttl b/plugins/eg-midigate.lv2/midigate.ttl new file mode 100644 index 0000000..e14a329 --- /dev/null +++ b/plugins/eg-midigate.lv2/midigate.ttl @@ -0,0 +1,56 @@ +# The same set of namespace prefixes with two additions for LV2 extensions this +# plugin uses: atom and urid. + +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix midi: . +@prefix rdfs: . +@prefix urid: . + + + a lv2:Plugin ; + doap:name "Example MIDI Gate" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; +# This plugin has three ports. There is an audio input and output as before, +# as well as a new AtomPort. An AtomPort buffer contains an Atom, which is a +# generic container for any type of data. In this case, we want to receive +# MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is +# a series of events with time stamps. +# +# Events themselves are also generic and can contain any type of data, but in +# this case we are only interested in MIDI events. The (optional) +# +atom:supports+ property describes which event types are supported. Though +# not required, this information should always be given so the host knows what +# types of event it can expect the plugin to understand. +# +# The (optional) +lv2:designation+ of this port is +lv2:control+, which +# indicates that this is the "main" control port where the host should send +# events it expects to configure the plugin, in this case changing the MIDI +# program. This is necessary since it is possible to have several MIDI input +# ports, though typically it is best to have one. + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 1 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . diff --git a/plugins/eg-midigate.lv2/waf b/plugins/eg-midigate.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-midigate.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg-midigate.lv2/wscript b/plugins/eg-midigate.lv2/wscript new file mode 100644 index 0000000..44336af --- /dev/null +++ b/plugins/eg-midigate.lv2/wscript @@ -0,0 +1,65 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-midigate.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Midigate Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-midigate.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-midigate.lv2) + for i in ['midigate.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = None + if autowaf.is_child: + includes = '../..' + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'midigate.c', + name = 'midigate', + target = '%s/midigate' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg-sampler.lv2/README.txt b/plugins/eg-sampler.lv2/README.txt new file mode 100644 index 0000000..4eed9e6 --- /dev/null +++ b/plugins/eg-sampler.lv2/README.txt @@ -0,0 +1,13 @@ +== Sampler == + +This plugin loads a single sample from a .wav file and plays it back when a MIDI +note on is received. Any sample on the system can be loaded via another event. +A Gtk UI is included which does this, but the host can as well. + +This plugin illustrates: + +- UI <==> Plugin communication via events +- Use of the worker extension for non-realtime tasks (sample loading) +- Use of the log extension to print log messages via the host +- Saving plugin state via the state extension +- Dynamic plugin control via the same properties saved to state diff --git a/plugins/eg-sampler.lv2/click.wav b/plugins/eg-sampler.lv2/click.wav new file mode 100644 index 0000000..520a18c Binary files /dev/null and b/plugins/eg-sampler.lv2/click.wav differ diff --git a/plugins/eg-sampler.lv2/manifest.ttl.in b/plugins/eg-sampler.lv2/manifest.ttl.in new file mode 100644 index 0000000..8a01428 --- /dev/null +++ b/plugins/eg-sampler.lv2/manifest.ttl.in @@ -0,0 +1,19 @@ +# Unlike the previous examples, this manifest lists more than one resource: the +# plugin as usual, and the UI. The descriptions are similar, but have +# different types, so the host can decide from this file alone whether or not +# it is interested, and avoid following the `rdfs:seeAlso` link if not (though +# in this case both are described in the same file). + +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + + + a ui:GtkUI ; + ui:binary ; + rdfs:seeAlso . diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c new file mode 100644 index 0000000..54da799 --- /dev/null +++ b/plugins/eg-sampler.lv2/sampler.c @@ -0,0 +1,483 @@ +/* + LV2 Sampler Example Plugin + Copyright 2011-2012 David Robillard + Copyright 2011 Gabriel M. Beddingfield + Copyright 2011 James Morris + + 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 +#include +#include +#ifndef __cplusplus +# include +#endif + +#include + +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/log/logger.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/ext/worker/worker.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "./uris.h" + +enum { + SAMPLER_CONTROL = 0, + SAMPLER_NOTIFY = 1, + SAMPLER_OUT = 2 +}; + +static const char* default_sample_file = "click.wav"; + +typedef struct { + SF_INFO info; // Info about sample from sndfile + float* data; // Sample data in float + char* path; // Path of file + uint32_t path_len; // Length of path +} Sample; + +typedef struct { + // Features + LV2_URID_Map* map; + LV2_Worker_Schedule* schedule; + LV2_Log_Log* log; + + // Forge for creating atoms + LV2_Atom_Forge forge; + + // Logger convenience API + LV2_Log_Logger logger; + + // Sample + Sample* sample; + + // Ports + const LV2_Atom_Sequence* control_port; + LV2_Atom_Sequence* notify_port; + float* output_port; + + // Forge frame for notify port (for writing worker replies) + LV2_Atom_Forge_Frame notify_frame; + + // URIs + SamplerURIs uris; + + // Current position in run() + uint32_t frame_offset; + + // Playback state + sf_count_t frame; + bool play; +} Sampler; + +/** + An atom-like message used internally to apply/free samples. + + This is only used internally to communicate with the worker, it is never + sent to the outside world via a port since it is not POD. It is convenient + to use an Atom header so actual atoms can be easily sent through the same + ringbuffer. +*/ +typedef struct { + LV2_Atom atom; + Sample* sample; +} SampleMessage; + +/** + Load a new sample and return it. + + Since this is of course not a real-time safe action, this is called in the + worker thread only. The sample is loaded and returned only, plugin state is + not modified. +*/ +static Sample* +load_sample(Sampler* self, const char* path) +{ + const size_t path_len = strlen(path); + + lv2_log_trace(&self->logger, "Loading sample %s\n", path); + + Sample* const sample = (Sample*)malloc(sizeof(Sample)); + SF_INFO* const info = &sample->info; + SNDFILE* const sndfile = sf_open(path, SFM_READ, info); + + if (!sndfile || !info->frames || (info->channels != 1)) { + lv2_log_error(&self->logger, "Failed to open sample '%s'\n", path); + free(sample); + return NULL; + } + + // Read data + float* const data = malloc(sizeof(float) * info->frames); + if (!data) { + lv2_log_error(&self->logger, "Failed to allocate memory for sample\n"); + return NULL; + } + sf_seek(sndfile, 0ul, SEEK_SET); + sf_read_float(sndfile, data, info->frames); + sf_close(sndfile); + + // Fill sample struct and return it + sample->data = data; + sample->path = (char*)malloc(path_len + 1); + sample->path_len = (uint32_t)path_len; + memcpy(sample->path, path, path_len + 1); + + return sample; +} + +static void +free_sample(Sampler* self, Sample* sample) +{ + if (sample) { + lv2_log_trace(&self->logger, "Freeing %s\n", sample->path); + free(sample->path); + free(sample->data); + free(sample); + } +} + +/** + Do work in a non-realtime thread. + + This is called for every piece of work scheduled in the audio thread using + self->schedule->schedule_work(). A reply can be sent back to the audio + thread using the provided respond function. +*/ +static LV2_Worker_Status +work(LV2_Handle instance, + LV2_Worker_Respond_Function respond, + LV2_Worker_Respond_Handle handle, + uint32_t size, + const void* data) +{ + Sampler* self = (Sampler*)instance; + const LV2_Atom* atom = (const LV2_Atom*)data; + if (atom->type == self->uris.eg_freeSample) { + // Free old sample + const SampleMessage* msg = (const SampleMessage*)data; + free_sample(self, msg->sample); + } else { + // Handle set message (load sample). + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data; + + // Get file path from message + const LV2_Atom* file_path = read_set_file(&self->uris, obj); + if (!file_path) { + return LV2_WORKER_ERR_UNKNOWN; + } + + // Load sample. + Sample* sample = load_sample(self, LV2_ATOM_BODY_CONST(file_path)); + if (sample) { + // Loaded sample, send it to run() to be applied. + respond(handle, sizeof(sample), &sample); + } + } + + return LV2_WORKER_SUCCESS; +} + +/** + Handle a response from work() in the audio thread. + + When running normally, this will be called by the host after run(). When + freewheeling, this will be called immediately at the point the work was + scheduled. +*/ +static LV2_Worker_Status +work_response(LV2_Handle instance, + uint32_t size, + const void* data) +{ + Sampler* self = (Sampler*)instance; + + SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample }, + self->sample }; + + // Send a message to the worker to free the current sample + self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); + + // Install the new sample + self->sample = *(Sample*const*)data; + + // Send a notification that we're using a new sample. + lv2_atom_forge_frame_time(&self->forge, self->frame_offset); + write_set_file(&self->forge, &self->uris, + self->sample->path, + self->sample->path_len); + + return LV2_WORKER_SUCCESS; +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Sampler* self = (Sampler*)instance; + switch (port) { + case SAMPLER_CONTROL: + self->control_port = (const LV2_Atom_Sequence*)data; + break; + case SAMPLER_NOTIFY: + self->notify_port = (LV2_Atom_Sequence*)data; + break; + case SAMPLER_OUT: + self->output_port = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + // Allocate and initialise instance structure. + Sampler* self = (Sampler*)malloc(sizeof(Sampler)); + if (!self) { + return NULL; + } + memset(self, 0, sizeof(Sampler)); + + // Get host features + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + self->map = (LV2_URID_Map*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_WORKER__schedule)) { + self->schedule = (LV2_Worker_Schedule*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { + self->log = (LV2_Log_Log*)features[i]->data; + } + } + if (!self->map) { + lv2_log_error(&self->logger, "Missing feature urid:map\n"); + goto fail; + } else if (!self->schedule) { + lv2_log_error(&self->logger, "Missing feature work:schedule\n"); + goto fail; + } + + // Map URIs and initialise forge/logger + map_sampler_uris(self->map, &self->uris); + lv2_atom_forge_init(&self->forge, self->map); + lv2_log_logger_init(&self->logger, self->map, self->log); + + // Load the default sample file + const size_t path_len = strlen(path); + const size_t file_len = strlen(default_sample_file); + const size_t len = path_len + file_len; + char* sample_path = (char*)malloc(len + 1); + snprintf(sample_path, len + 1, "%s%s", path, default_sample_file); + self->sample = load_sample(self, sample_path); + free(sample_path); + + return (LV2_Handle)self; + +fail: + free(self); + return 0; +} + +static void +cleanup(LV2_Handle instance) +{ + Sampler* self = (Sampler*)instance; + free_sample(self, self->sample); + free(self); +} + +static void +run(LV2_Handle instance, + uint32_t sample_count) +{ + Sampler* self = (Sampler*)instance; + SamplerURIs* uris = &self->uris; + sf_count_t start_frame = 0; + sf_count_t pos = 0; + float* output = self->output_port; + + // Set up forge to write directly to notify output port. + const uint32_t notify_capacity = self->notify_port->atom.size; + lv2_atom_forge_set_buffer(&self->forge, + (uint8_t*)self->notify_port, + notify_capacity); + + // Start a sequence in the notify output port. + lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0); + + // Read incoming events + LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { + self->frame_offset = ev->time.frames; + if (ev->body.type == uris->midi_Event) { + const uint8_t* const msg = (const uint8_t*)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + start_frame = ev->time.frames; + self->frame = 0; + self->play = true; + break; + default: + break; + } + } else if (lv2_atom_forge_is_object_type(&self->forge, ev->body.type)) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; + if (obj->body.otype == uris->patch_Set) { + // Received a set message, send it to the worker. + lv2_log_trace(&self->logger, "Queueing set message\n"); + self->schedule->schedule_work(self->schedule->handle, + lv2_atom_total_size(&ev->body), + &ev->body); + } else { + lv2_log_trace(&self->logger, + "Unknown object type %d\n", obj->body.otype); + } + } else { + lv2_log_trace(&self->logger, + "Unknown event type %d\n", ev->body.type); + } + } + + // Render the sample (possibly already in progress) + if (self->play) { + uint32_t f = self->frame; + const uint32_t lf = self->sample->info.frames; + + for (pos = 0; pos < start_frame; ++pos) { + output[pos] = 0; + } + + for (; pos < sample_count && f < lf; ++pos, ++f) { + output[pos] = self->sample->data[f]; + } + + self->frame = f; + + if (f == lf) { + self->play = false; + } + } + + // Add zeros to end if sample not long enough (or not playing) + for (; pos < sample_count; ++pos) { + output[pos] = 0.0f; + } +} + +static LV2_State_Status +save(LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + Sampler* self = (Sampler*)instance; + if (!self->sample) { + return LV2_STATE_SUCCESS; + } + + LV2_State_Map_Path* map_path = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_STATE__mapPath)) { + map_path = (LV2_State_Map_Path*)features[i]->data; + } + } + + char* apath = map_path->abstract_path(map_path->handle, self->sample->path); + + store(handle, + self->uris.eg_sample, + apath, + strlen(self->sample->path) + 1, + self->uris.atom_Path, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + free(apath); + + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + Sampler* self = (Sampler*)instance; + + size_t size; + uint32_t type; + uint32_t valflags; + + const void* value = retrieve( + handle, + self->uris.eg_sample, + &size, &type, &valflags); + + if (value) { + const char* path = (const char*)value; + lv2_log_trace(&self->logger, "Restoring file %s\n", path); + free_sample(self, self->sample); + self->sample = load_sample(self, path); + } + + return LV2_STATE_SUCCESS; +} + +static const void* +extension_data(const char* uri) +{ + static const LV2_State_Interface state = { save, restore }; + static const LV2_Worker_Interface worker = { work, work_response, NULL }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } else if (!strcmp(uri, LV2_WORKER__interface)) { + return &worker; + } + return NULL; +} + +static const LV2_Descriptor descriptor = { + EG_SAMPLER_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl new file mode 100644 index 0000000..2a89dd2 --- /dev/null +++ b/plugins/eg-sampler.lv2/sampler.ttl @@ -0,0 +1,67 @@ +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix patch: . +@prefix rdfs: . +@prefix state: . +@prefix ui: . +@prefix urid: . +@prefix work: . + + + a lv2:Parameter ; + rdfs:label "sample" ; + rdfs:range atom:Path . + + + a lv2:Plugin ; + doap:name "Example Sampler" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map , + work:schedule ; + lv2:optionalFeature lv2:hardRTCapable , + state:loadDefaultState ; + lv2:extensionData state:interface , + work:interface ; + ui:ui ; + patch:writable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports , + patch:Message ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] ; + state:state [ + + ] . + + + a ui:GtkUI ; + lv2:requiredFeature urid:map ; + lv2:extensionData ui:showInterface ; + ui:portNotification [ + ui:plugin ; + lv2:symbol "notify" ; + ui:notifyType atom:Blank + ] . diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c new file mode 100644 index 0000000..d691c98 --- /dev/null +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -0,0 +1,241 @@ +/* + LV2 Sampler Example Plugin UI + Copyright 2011-2012 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. +*/ + +#include + +#include + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#include "./uris.h" + +#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" + +typedef struct { + LV2_Atom_Forge forge; + + LV2_URID_Map* map; + SamplerURIs uris; + + LV2UI_Write_Function write; + LV2UI_Controller controller; + + GtkWidget* box; + GtkWidget* button; + GtkWidget* label; + GtkWidget* window; /* For optional show interface. */ +} SamplerUI; + +static void +on_load_clicked(GtkWidget* widget, + void* handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + /* Create a dialog to select a sample file. */ + GtkWidget* dialog = gtk_file_chooser_dialog_new( + "Load Sample", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + /* Run the dialog, and return if it is cancelled. */ + if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { + gtk_widget_destroy(dialog); + return; + } + + /* Get the file path from the dialog. */ + char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + + /* Got what we need, destroy the dialog. */ + gtk_widget_destroy(dialog); + +#define OBJ_BUF_SIZE 1024 + uint8_t obj_buf[OBJ_BUF_SIZE]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE); + + LV2_Atom* msg = write_set_file(&ui->forge, &ui->uris, + filename, strlen(filename)); + + ui->write(ui->controller, 0, lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); + + g_free(filename); +} + +static LV2UI_Handle +instantiate(const LV2UI_Descriptor* descriptor, + const char* plugin_uri, + const char* bundle_path, + LV2UI_Write_Function write_function, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) +{ + SamplerUI* ui = (SamplerUI*)malloc(sizeof(SamplerUI)); + ui->map = NULL; + ui->write = write_function; + ui->controller = controller; + ui->box = NULL; + ui->button = NULL; + ui->label = NULL; + ui->window = NULL; + + *widget = NULL; + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + ui->map = (LV2_URID_Map*)features[i]->data; + } + } + + if (!ui->map) { + fprintf(stderr, "sampler_ui: Host does not support urid:Map\n"); + free(ui); + return NULL; + } + + map_sampler_uris(ui->map, &ui->uris); + + lv2_atom_forge_init(&ui->forge, ui->map); + + ui->box = gtk_vbox_new(FALSE, 4); + ui->label = gtk_label_new("?"); + ui->button = gtk_button_new_with_label("Load Sample"); + gtk_box_pack_start(GTK_BOX(ui->box), ui->label, TRUE, TRUE, 4); + gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, FALSE, 4); + g_signal_connect(ui->button, "clicked", + G_CALLBACK(on_load_clicked), + ui); + + *widget = ui->box; + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + gtk_widget_destroy(ui->button); + free(ui); +} + +static void +port_event(LV2UI_Handle handle, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + SamplerUI* ui = (SamplerUI*)handle; + if (format == ui->uris.atom_eventTransfer) { + const LV2_Atom* atom = (const LV2_Atom*)buffer; + if (atom->type == ui->uris.atom_Blank) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + const LV2_Atom* file_uri = read_set_file(&ui->uris, obj); + if (!file_uri) { + fprintf(stderr, "Unknown message sent to UI.\n"); + return; + } + + const char* uri = (const char*)LV2_ATOM_BODY_CONST(file_uri); + gtk_label_set_text(GTK_LABEL(ui->label), uri); + } else { + fprintf(stderr, "Unknown message type.\n"); + } + } else { + fprintf(stderr, "Unknown format.\n"); + } +} + +/* Optional non-embedded UI show interface. */ +static int +ui_show(LV2UI_Handle handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + int argc = 0; + gtk_init(&argc, NULL); + + ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_add(GTK_CONTAINER(ui->window), ui->box); + gtk_widget_show_all(ui->window); + gtk_window_present(GTK_WINDOW(ui->window)); + + return 0; +} + +/* Optional non-embedded UI hide interface. */ +static int +ui_hide(LV2UI_Handle handle) +{ + return 0; +} + +/* Idle interface for optional non-embedded UI. */ +static int +ui_idle(LV2UI_Handle handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + if (ui->window) { + gtk_main_iteration(); + } + return 0; +} + +static const void* +extension_data(const char* uri) +{ + static const LV2UI_Show_Interface show = { ui_show, ui_hide }; + static const LV2UI_Idle_Interface idle = { ui_idle }; + if (!strcmp(uri, LV2_UI__showInterface)) { + return &show; + } else if (!strcmp(uri, LV2_UI__idleInterface)) { + return &idle; + } + return NULL; +} + +static const LV2UI_Descriptor descriptor = { + SAMPLER_UI_URI, + instantiate, + cleanup, + port_event, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h new file mode 100644 index 0000000..8e9faee --- /dev/null +++ b/plugins/eg-sampler.lv2/uris.h @@ -0,0 +1,135 @@ +/* + LV2 Sampler Example Plugin + Copyright 2011-2012 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. +*/ + +#ifndef SAMPLER_URIS_H +#define SAMPLER_URIS_H + +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" + +#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" +#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" +#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample" +#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample" + +typedef struct { + LV2_URID atom_Blank; + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + LV2_URID eg_applySample; + LV2_URID eg_sample; + LV2_URID eg_freeSample; + LV2_URID midi_Event; + LV2_URID patch_Set; + LV2_URID patch_property; + LV2_URID patch_value; +} SamplerURIs; + +static inline void +map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) +{ + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); + uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); + uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); + uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); + uris->eg_applySample = map->map(map->handle, EG_SAMPLER__applySample); + uris->eg_freeSample = map->map(map->handle, EG_SAMPLER__freeSample); + uris->eg_sample = map->map(map->handle, EG_SAMPLER__sample); + uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); + uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris->patch_property = map->map(map->handle, LV2_PATCH__property); + uris->patch_value = map->map(map->handle, LV2_PATCH__value); +} + +/** + * Write a message like the following to @p forge: + * [] + * a patch:Set ; + * patch:property eg:sample ; + * patch:value . + */ +static inline LV2_Atom* +write_set_file(LV2_Atom_Forge* forge, + const SamplerURIs* uris, + const char* filename, + const uint32_t filename_len) +{ + LV2_Atom_Forge_Frame frame; + LV2_Atom* set = (LV2_Atom*)lv2_atom_forge_object( + forge, &frame, 0, uris->patch_Set); + + lv2_atom_forge_key(forge, uris->patch_property); + lv2_atom_forge_urid(forge, uris->eg_sample); + lv2_atom_forge_key(forge, uris->patch_value); + lv2_atom_forge_path(forge, filename, filename_len); + + lv2_atom_forge_pop(forge, &frame); + + return set; +} + +/** + * Get the file path from a message like: + * [] + * a patch:Set ; + * patch:property eg:sample ; + * patch:value . + */ +static inline const LV2_Atom* +read_set_file(const SamplerURIs* uris, + const LV2_Atom_Object* obj) +{ + if (obj->body.otype != uris->patch_Set) { + fprintf(stderr, "Ignoring unknown message type %d\n", obj->body.otype); + return NULL; + } + + /* Get property URI. */ + const LV2_Atom* property = NULL; + lv2_atom_object_get(obj, uris->patch_property, &property, 0); + if (!property) { + fprintf(stderr, "Malformed set message has no body.\n"); + return NULL; + } else if (property->type != uris->atom_URID) { + fprintf(stderr, "Malformed set message has non-URID property.\n"); + return NULL; + } else if (((const LV2_Atom_URID*)property)->body != uris->eg_sample) { + fprintf(stderr, "Set message for unknown property.\n"); + return NULL; + } + + /* Get value. */ + const LV2_Atom* file_path = NULL; + lv2_atom_object_get(obj, uris->patch_value, &file_path, 0); + if (!file_path) { + fprintf(stderr, "Malformed set message has no value.\n"); + return NULL; + } else if (file_path->type != uris->atom_Path) { + fprintf(stderr, "Set message value is not a Path.\n"); + return NULL; + } + + return file_path; +} + +#endif /* SAMPLER_URIS_H */ diff --git a/plugins/eg-sampler.lv2/waf b/plugins/eg-sampler.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-sampler.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg-sampler.lv2/wscript b/plugins/eg-sampler.lv2/wscript new file mode 100644 index 0000000..732c904 --- /dev/null +++ b/plugins/eg-sampler.lv2/wscript @@ -0,0 +1,80 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-sampler.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Sampler Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') + + autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', + atleast_version='1.0.0', mandatory=True) + autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', + atleast_version='2.18.0', mandatory=False) + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-sampler.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-sampler.lv2) + for i in ['sampler.ttl', 'click.wav']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'sampler.c', + name = 'sampler', + target = '%s/sampler' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'SNDFILE LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + + # Build UI library + if bld.is_defined('HAVE_GTK2'): + obj = bld(features = 'c cshlib', + source = 'sampler_ui.c', + name = 'sampler_ui', + target = '%s/sampler_ui' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'GTK2 LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg-scope.lv2/README.txt b/plugins/eg-scope.lv2/README.txt new file mode 100644 index 0000000..122794c --- /dev/null +++ b/plugins/eg-scope.lv2/README.txt @@ -0,0 +1,32 @@ +== Simple Oscilloscope == + +This plugin displays the waveform of an incoming audio signal using a simple +GTK+Cairo GUI. + +This plugin illustrates: + +- UI <==> Plugin communication via http://lv2plug.in/ns/ext/atom/[LV2 Atom] events +- Atom vector usage and resize-port extension +- Save/Restore UI state by communicating state to backend +- Saving simple key/value state via the http://lv2plug.in/ns/ext/state/[LV2 State] extension +- Cairo drawing and partial exposure + +This plugin intends to outline the basics for building visualization plugins +that rely on atom communication. The UI looks like an oscilloscope, but is not +a real oscilloscope implementation: + +- There is no display synchronisation, results will depend on LV2 host. +- It displays raw audio samples, which a proper scope must not do. +- The display itself just connects min/max line segments. +- No triggering or synchronization. +- No labels, no scale, no calibration, no markers, no numeric readout, etc. + +Addressing these issues is beyond the scope of this example. + +Please see http://lac.linuxaudio.org/2013/papers/36.pdf for scope design, +https://wiki.xiph.org/Videos/Digital_Show_and_Tell for background information, +and http://lists.lv2plug.in/pipermail/devel-lv2plug.in/2013-November/000545.html +for general LV2 related conceptual criticism regarding real-time visualizations. + +A proper oscilloscope based on this example can be found at +https://github.com/x42/sisco.lv2 diff --git a/plugins/eg-scope.lv2/examploscope.c b/plugins/eg-scope.lv2/examploscope.c new file mode 100644 index 0000000..6d1e953 --- /dev/null +++ b/plugins/eg-scope.lv2/examploscope.c @@ -0,0 +1,423 @@ +/* + Copyright 2013 Robin Gareus + + 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 +#include +#include + +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/log/logger.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "./uris.h" + +/** + ==== Private Plugin Instance Structure ==== + + In addition to the usual port buffers and features, this plugin stores the + state of the UI here, so it can be opened and closed without losing the + current settings. The UI state is communicated between the plugin and the + UI using atom messages via a sequence port, similarly to MIDI I/O. +*/ +typedef struct { + // Port buffers + float* input[2]; + float* output[2]; + const LV2_Atom_Sequence* control; + LV2_Atom_Sequence* notify; + + // Atom forge and URI mapping + LV2_URID_Map* map; + ScoLV2URIs uris; + LV2_Atom_Forge forge; + LV2_Atom_Forge_Frame frame; + + // Log feature and convenience API + LV2_Log_Log* log; + LV2_Log_Logger logger; + + // Instantiation settings + uint32_t n_channels; + double rate; + + // UI state + bool ui_active; + bool send_settings_to_ui; + float ui_amp; + uint32_t ui_spp; +} EgScope; + +/** ==== Port Indices ==== */ +typedef enum { + SCO_CONTROL = 0, // Event input + SCO_NOTIFY = 1, // Event output + SCO_INPUT0 = 2, // Audio input 0 + SCO_OUTPUT0 = 3, // Audio output 0 + SCO_INPUT1 = 4, // Audio input 1 (stereo variant) + SCO_OUTPUT1 = 5, // Audio input 2 (stereo variant) +} PortIndex; + +/** ==== Instantiate Method ==== */ +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + (void)descriptor; // Unused variable + (void)bundle_path; // Unused variable + + // Allocate and initialise instance structure. + EgScope* self = (EgScope*)calloc(1, sizeof(EgScope)); + if (!self) { + return NULL; + } + + // Get host features + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + self->map = (LV2_URID_Map*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { + self->log = (LV2_Log_Log*)features[i]->data; + } + } + + if (!self->map) { + fprintf(stderr, "EgScope.lv2 error: Host does not support urid:map\n"); + free(self); + return NULL; + } + + // Decide which variant to use depending on the plugin URI + if (!strcmp(descriptor->URI, SCO_URI "#Stereo")) { + self->n_channels = 2; + } else if (!strcmp(descriptor->URI, SCO_URI "#Mono")) { + self->n_channels = 1; + } else { + free(self); + return NULL; + } + + // Initialise local variables + self->ui_active = false; + self->send_settings_to_ui = false; + self->rate = rate; + + // Set default UI settings + self->ui_spp = 50; + self->ui_amp = 1.0; + + // Map URIs and initialise forge/logger + map_sco_uris(self->map, &self->uris); + lv2_atom_forge_init(&self->forge, self->map); + lv2_log_logger_init(&self->logger, self->map, self->log); + + return (LV2_Handle)self; +} + +/** ==== Connect Port Method ==== */ +static void +connect_port(LV2_Handle handle, + uint32_t port, + void* data) +{ + EgScope* self = (EgScope*)handle; + + switch ((PortIndex)port) { + case SCO_CONTROL: + self->control = (const LV2_Atom_Sequence*)data; + break; + case SCO_NOTIFY: + self->notify = (LV2_Atom_Sequence*)data; + break; + case SCO_INPUT0: + self->input[0] = (float*)data; + break; + case SCO_OUTPUT0: + self->output[0] = (float*)data; + break; + case SCO_INPUT1: + self->input[1] = (float*)data; + break; + case SCO_OUTPUT1: + self->output[1] = (float*)data; + break; + } +} + +/** + ==== Utility Function: `tx_rawaudio` ==== + + This function forges a message for sending a vector of raw data. The object + is a http://lv2plug.in/ns/ext/atom#Blank[Blank] with a few properties, like: + [source,n3] + -------- + [] + a sco:RawAudio ; + sco:channelID 0 ; + sco:audioData [ 0.0, 0.0, ... ] . + -------- + + where the value of the `sco:audioData` property, `[ 0.0, 0.0, ... ]`, is a + http://lv2plug.in/ns/ext/atom#Vector[Vector] of + http://lv2plug.in/ns/ext/atom#Float[Float]. +*/ +static void +tx_rawaudio(LV2_Atom_Forge* forge, + ScoLV2URIs* uris, + const int32_t channel, + const size_t n_samples, + const float* data) +{ + LV2_Atom_Forge_Frame frame; + + // Forge container object of type 'RawAudio' + lv2_atom_forge_frame_time(forge, 0); + lv2_atom_forge_object(forge, &frame, 0, uris->RawAudio); + + // Add integer 'channelID' property + lv2_atom_forge_key(forge, uris->channelID); + lv2_atom_forge_int(forge, channel); + + // Add vector of floats 'audioData' property + lv2_atom_forge_key(forge, uris->audioData); + lv2_atom_forge_vector( + forge, sizeof(float), uris->atom_Float, n_samples, data); + + // Close off object + lv2_atom_forge_pop(forge, &frame); +} + +/** ==== Run Method ==== */ +static void +run(LV2_Handle handle, uint32_t n_samples) +{ + EgScope* self = (EgScope*)handle; + + /* Ensure notify port buffer is large enough to hold all audio-samples and + configuration settings. A minimum size was requested in the .ttl file, + but check here just to be sure. + + TODO: Explain these magic numbers. + */ + const size_t size = (sizeof(float) * n_samples + 64) * self->n_channels; + const uint32_t space = self->notify->atom.size; + if (space < size + 128) { + /* Insufficient space, report error and do nothing. Note that a + real-time production plugin mustn't call log functions in run(), but + this can be useful for debugging and example purposes. + */ + lv2_log_error(&self->logger, "Buffer size is insufficient\n"); + return; + } + + // Prepare forge buffer and initialize atom-sequence + lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->notify, space); + lv2_atom_forge_sequence_head(&self->forge, &self->frame, 0); + + /* Send settings to UI + + The plugin can continue to run while the UI is closed and re-opened. + The state and settings of the UI are kept here and transmitted to the UI + every time it asks for them or if the user initializes a 'load preset'. + */ + if (self->send_settings_to_ui && self->ui_active) { + self->send_settings_to_ui = false; + // Forge container object of type 'ui_state' + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_frame_time(&self->forge, 0); + lv2_atom_forge_object(&self->forge, &frame, 0, self->uris.ui_State); + + // Add UI state as properties + lv2_atom_forge_key(&self->forge, self->uris.ui_spp); + lv2_atom_forge_int(&self->forge, self->ui_spp); + lv2_atom_forge_key(&self->forge, self->uris.ui_amp); + lv2_atom_forge_float(&self->forge, self->ui_amp); + lv2_atom_forge_key(&self->forge, self->uris.param_sampleRate); + lv2_atom_forge_float(&self->forge, self->rate); + lv2_atom_forge_pop(&self->forge, &frame); + } + + // Process incoming events from GUI + if (self->control) { + const LV2_Atom_Event* ev = lv2_atom_sequence_begin( + &(self->control)->body); + // For each incoming message... + while (!lv2_atom_sequence_is_end( + &self->control->body, self->control->atom.size, ev)) { + // If the event is an atom:Blank object + if (ev->body.type == self->uris.atom_Blank) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; + if (obj->body.otype == self->uris.ui_On) { + // If the object is a ui-on, the UI was activated + self->ui_active = true; + self->send_settings_to_ui = true; + } else if (obj->body.otype == self->uris.ui_Off) { + // If the object is a ui-off, the UI was closed + self->ui_active = false; + } else if (obj->body.otype == self->uris.ui_State) { + // If the object is a ui-state, it's the current UI settings + const LV2_Atom* spp = NULL; + const LV2_Atom* amp = NULL; + lv2_atom_object_get(obj, self->uris.ui_spp, &spp, + self->uris.ui_amp, &, + 0); + if (spp) { + self->ui_spp = ((const LV2_Atom_Int*)spp)->body; + } + if (amp) { + self->ui_amp = ((const LV2_Atom_Float*)amp)->body; + } + } + } + ev = lv2_atom_sequence_next(ev); + } + } + + // Process audio data + for (uint32_t c = 0; c < self->n_channels; ++c) { + if (self->ui_active) { + // If UI is active, send raw audio data to UI + tx_rawaudio(&self->forge, &self->uris, c, n_samples, self->input[c]); + } + // If not processing audio in-place, forward audio + if (self->input[c] != self->output[c]) { + memcpy(self->output[c], self->input[c], sizeof(float) * n_samples); + } + } + + // Close off sequence + lv2_atom_forge_pop(&self->forge, &self->frame); +} + +static void +cleanup(LV2_Handle handle) +{ + free(handle); +} + + +/** + ==== State Methods ==== + + This plugin's state consists of two basic properties: one `int` and one + `float`. No files are used. Note these values are POD, but not portable, + since different machines may have a different integer endianness or floating + point format. However, since standard Atom types are used, a good host will + be able to save them portably as text anyway. +*/ +static LV2_State_Status +state_save(LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + EgScope* self = (EgScope*)instance; + if (!self) { + return LV2_STATE_SUCCESS; + } + + store(handle, self->uris.ui_spp, + (void*)&self->ui_spp, sizeof(uint32_t), + self->uris.atom_Int, + LV2_STATE_IS_POD); + + store(handle, self->uris.ui_amp, + (void*)&self->ui_amp, sizeof(float), + self->uris.atom_Float, + LV2_STATE_IS_POD); + + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +state_restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + EgScope* self = (EgScope*)instance; + + size_t size; + uint32_t type; + uint32_t valflags; + + const void* spp = retrieve( + handle, self->uris.ui_spp, &size, &type, &valflags); + if (spp && size == sizeof(uint32_t) && type == self->uris.atom_Int) { + self->ui_spp = *((const uint32_t*)spp); + self->send_settings_to_ui = true; + } + + const void* amp = retrieve( + handle, self->uris.ui_amp, &size, &type, &valflags); + if (amp && size == sizeof(float) && type == self->uris.atom_Float) { + self->ui_amp = *((const float*)amp); + self->send_settings_to_ui = true; + } + + return LV2_STATE_SUCCESS; +} + +static const void* +extension_data(const char* uri) +{ + static const LV2_State_Interface state = { state_save, state_restore }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } + return NULL; +} + +/** ==== Plugin Descriptors ==== */ +static const LV2_Descriptor descriptor_mono = { + SCO_URI "#Mono", + instantiate, + connect_port, + NULL, + run, + NULL, + cleanup, + extension_data +}; + +static const LV2_Descriptor descriptor_stereo = { + SCO_URI "#Stereo", + instantiate, + connect_port, + NULL, + run, + NULL, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor_mono; + case 1: + return &descriptor_stereo; + default: + return NULL; + } +} diff --git a/plugins/eg-scope.lv2/examploscope.ttl.in b/plugins/eg-scope.lv2/examploscope.ttl.in new file mode 100644 index 0000000..0b76962 --- /dev/null +++ b/plugins/eg-scope.lv2/examploscope.ttl.in @@ -0,0 +1,130 @@ +@prefix atom: . +@prefix bufsz: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . +@prefix urid: . +@prefix rsz: . +@prefix state: . +@prefix egscope: . + + + a foaf:Person ; + foaf:name "Robin Gareus" ; + foaf:mbox ; + foaf:homepage . + + + a doap:Project ; + doap:maintainer ; + doap:name "Example Scope" . + +egscope:Mono + a lv2:Plugin, lv2:AnalyserPlugin ; + doap:name "Example Scope (Mono)" ; + lv2:project ; + doap:license ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:extensionData state:interface ; + ui:ui egscope:ui ; + lv2:port [ + a atom:AtomPort , + lv2:InputPort ; + atom:bufferType atom:Sequence ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a atom:AtomPort , + lv2:OutputPort ; + atom:bufferType atom:Sequence ; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + # 8192 * sizeof(float) + LV2-Atoms + rsz:minimumSize 32832; + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 2 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 3 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . + + +egscope:Stereo + a lv2:Plugin, lv2:AnalyserPlugin ; + doap:name "Example Scope (Stereo)" ; + lv2:project ; + doap:license ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:extensionData state:interface ; + ui:ui egscope:ui ; + lv2:port [ + a atom:AtomPort , + lv2:InputPort ; + atom:bufferType atom:Sequence ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a atom:AtomPort , + lv2:OutputPort ; + atom:bufferType atom:Sequence ; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + rsz:minimumSize 65664; + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 2 ; + lv2:symbol "in0" ; + lv2:name "InL" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 3 ; + lv2:symbol "out0" ; + lv2:name "OutL" + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 4 ; + lv2:symbol "in1" ; + lv2:name "InR" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 5 ; + lv2:symbol "out1" ; + lv2:name "OutR" + ] . + + +egscope:ui + a ui:GtkUI ; + lv2:requiredFeature urid:map ; + ui:portNotification [ + ui:plugin egscope:Mono ; + lv2:symbol "notify" ; + ui:notifyType atom:Blank + ] , [ + ui:plugin egscope:Stereo ; + lv2:symbol "notify" ; + ui:notifyType atom:Blank + ] . diff --git a/plugins/eg-scope.lv2/examploscope_ui.c b/plugins/eg-scope.lv2/examploscope_ui.c new file mode 100644 index 0000000..a8c1e2c --- /dev/null +++ b/plugins/eg-scope.lv2/examploscope_ui.c @@ -0,0 +1,655 @@ +/* + Copyright 2013 Robin Gareus + + 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 +#include + +#include +#include + +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" +#include "./uris.h" + +// Drawing area size +#define DAWIDTH (640) +#define DAHEIGHT (200) + +/** + Max continuous points on path. Many short-path segments are + expensive|inefficient long paths are not supported by all surfaces (usually + its a miter - not point - limit, depending on used cairo backend) +*/ +#define MAX_CAIRO_PATH (128) + +/** + Representation of the raw audio-data for display (min | max) values for a + given 'index' position. +*/ +typedef struct { + float data_min[DAWIDTH]; + float data_max[DAWIDTH]; + + uint32_t idx; + uint32_t sub; +} ScoChan; + +typedef struct { + LV2_Atom_Forge forge; + LV2_URID_Map* map; + ScoLV2URIs uris; + + LV2UI_Write_Function write; + LV2UI_Controller controller; + + GtkWidget* hbox; + GtkWidget* vbox; + GtkWidget* sep[2]; + GtkWidget* darea; + GtkWidget* btn_pause; + GtkWidget* lbl_speed; + GtkWidget* lbl_amp; + GtkWidget* spb_speed; + GtkWidget* spb_amp; + GtkAdjustment* spb_speed_adj; + GtkAdjustment* spb_amp_adj; + + ScoChan chn[2]; + uint32_t stride; + uint32_t n_channels; + bool paused; + float rate; +} EgScopeUI; + + +/** Send current UI settings to backend. */ +static void +send_ui_state(LV2UI_Handle handle) +{ + EgScopeUI* ui = (EgScopeUI*)handle; + const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); + + // Use local buffer on the stack to build atom + uint8_t obj_buf[1024]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + + // Start a ui:State object + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( + &ui->forge, &frame, 0, ui->uris.ui_State); + + // msg[samples-per-pixel] = integer + lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp); + lv2_atom_forge_int(&ui->forge, ui->stride); + + // msg[amplitude] = float + lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp); + lv2_atom_forge_float(&ui->forge, gain); + + // Finish ui:State object + lv2_atom_forge_pop(&ui->forge, &frame); + + // Send message to plugin port '0' + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); +} + +/** Notify backend that UI is closed. */ +static void +send_ui_disable(LV2UI_Handle handle) +{ + EgScopeUI* ui = (EgScopeUI*)handle; + send_ui_state(handle); + + uint8_t obj_buf[64]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( + &ui->forge, &frame, 0, ui->uris.ui_Off); + lv2_atom_forge_pop(&ui->forge, &frame); + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); +} + +/** + Notify backend that UI is active. + + The plugin should send state and enable data transmission. +*/ +static void +send_ui_enable(LV2UI_Handle handle) +{ + EgScopeUI* ui = (EgScopeUI*)handle; + + uint8_t obj_buf[64]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( + &ui->forge, &frame, 0, ui->uris.ui_On); + lv2_atom_forge_pop(&ui->forge, &frame); + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); +} + +/** Gtk widget callback. */ +static gboolean +on_cfg_changed(GtkWidget* widget, gpointer data) +{ + send_ui_state(data); + return TRUE; +} + +/** + Gdk drawing area draw callback. + + Called in Gtk's main thread and uses Cairo to draw the data. +*/ +static gboolean +on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data) +{ + EgScopeUI* ui = (EgScopeUI*)data; + const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); + + // Get cairo type for the gtk window + cairo_t* cr; + cr = gdk_cairo_create(ui->darea->window); + + // Limit cairo-drawing to exposed area + cairo_rectangle(cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height); + cairo_clip(cr); + + // Clear background + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels); + cairo_fill(cr); + + cairo_set_line_width(cr, 1.0); + + const uint32_t start = ev->area.x; + const uint32_t end = ev->area.x + ev->area.width; + + assert(start < DAWIDTH); + assert(end <= DAWIDTH); + assert(start < end); + + for (uint32_t c = 0; c < ui->n_channels; ++c) { + ScoChan* chn = &ui->chn[c]; + + /* Drawing area Y-position of given sample-value. + * Note: cairo-pixel at 0 spans -0.5 .. +0.5, hence (DAHEIGHT / 2.0 -0.5) + * also the cairo Y-axis points upwards (hence 'minus value') + * + * == ( DAHEIGHT * (CHN) // channel offset + * + (DAHEIGHT / 2) - 0.5 // vertical center -- '0' + * - (DAHEIGHT / 2) * (VAL) * (GAIN) + * ) + */ + const float chn_y_offset = DAHEIGHT * c + DAHEIGHT * 0.5f - 0.5f; + const float chn_y_scale = DAHEIGHT * 0.5f * gain; + +#define CYPOS(VAL) (chn_y_offset - (VAL) * chn_y_scale) + + cairo_save(cr); + + /* Restrict drawing to current channel area, don't bleed drawing into + neighboring channels. */ + cairo_rectangle(cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT); + cairo_clip(cr); + + // Set color of wave-form + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + + /* This is a somewhat 'smart' mechanism to plot audio data using + alternating up/down line-directions. It works well for both cases: + 1 pixel <= 1 sample and 1 pixel represents more than 1 sample, but + is not ideal for either. */ + if (start == chn->idx) { + cairo_move_to(cr, start - 0.5, CYPOS(0)); + } else { + cairo_move_to(cr, start - 0.5, CYPOS(chn->data_max[start])); + } + + uint32_t pathlength = 0; + for (uint32_t i = start; i < end; ++i) { + if (i == chn->idx) { + continue; + } else if (i % 2) { + cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); + cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); + ++pathlength; + } else { + cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); + cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); + ++pathlength; + } + + /** Limit the max cairo path length. This is an optimization trade + off: too short path: high load CPU/GPU load. too-long path: + bad anti-aliasing, or possibly lost points */ + if (pathlength > MAX_CAIRO_PATH) { + pathlength = 0; + cairo_stroke(cr); + if (i % 2) { + cairo_move_to(cr, i - .5, CYPOS(chn->data_max[i])); + } else { + cairo_move_to(cr, i - .5, CYPOS(chn->data_min[i])); + } + } + } + + if (pathlength > 0) { + cairo_stroke(cr); + } + + // Draw current position vertical line if display is slow + if (ui->stride >= ui->rate / 4800.0f || ui->paused) { + cairo_set_source_rgba(cr, .9, .2, .2, .6); + cairo_move_to(cr, chn->idx - .5, DAHEIGHT * c); + cairo_line_to(cr, chn->idx - .5, DAHEIGHT * (c + 1)); + cairo_stroke(cr); + } + + // Undo the 'clipping' restriction + cairo_restore(cr); + + // Channel separator + if (c > 0) { + cairo_set_source_rgba(cr, .5, .5, .5, 1.0); + cairo_move_to(cr, 0, DAHEIGHT * c - .5); + cairo_line_to(cr, DAWIDTH, DAHEIGHT * c - .5); + cairo_stroke(cr); + } + + // Zero scale line + cairo_set_source_rgba(cr, .3, .3, .7, .5); + cairo_move_to(cr, 0, DAHEIGHT * (c + .5) - .5); + cairo_line_to(cr, DAWIDTH, DAHEIGHT * (c + .5) - .5); + cairo_stroke(cr); + } + + cairo_destroy(cr); + return TRUE; +} + +/** + Parse raw audio data and prepare for later drawing. + + Note this is a toy example, which is really a waveform display, not an + oscilloscope. A serious scope would not display samples as is. + + Signals above ~ 1/10 of the sampling-rate will not yield a useful visual + display and result in a rather unintuitive representation of the actual + waveform. + + Ideally the audio-data would be buffered and upsampled here and after that + written in a display buffer for later use. + + For more information, see + https://wiki.xiph.org/Videos/Digital_Show_and_Tell + http://lac.linuxaudio.org/2013/papers/36.pdf + and https://github.com/x42/sisco.lv2 +*/ +static int +process_channel(EgScopeUI* ui, + ScoChan* chn, + const size_t n_elem, + float const* data, + uint32_t* idx_start, + uint32_t* idx_end) +{ + int overflow = 0; + *idx_start = chn->idx; + for (size_t i = 0; i < n_elem; ++i) { + if (data[i] < chn->data_min[chn->idx]) { + chn->data_min[chn->idx] = data[i]; + } + if (data[i] > chn->data_max[chn->idx]) { + chn->data_max[chn->idx] = data[i]; + } + if (++chn->sub >= ui->stride) { + chn->sub = 0; + chn->idx = (chn->idx + 1) % DAWIDTH; + if (chn->idx == 0) { + ++overflow; + } + chn->data_min[chn->idx] = 1.0; + chn->data_max[chn->idx] = -1.0; + } + } + *idx_end = chn->idx; + return overflow; +} + +/** + Called via port_event() which is called by the host, typically at a rate of + around 25 FPS. +*/ +static void +update_scope(EgScopeUI* ui, + const int32_t channel, + const size_t n_elem, + float const* data) +{ + // Never trust input data which could lead to application failure. + if (channel < 0 || (uint32_t)channel > ui->n_channels) { + return; + } + + // Update state in sync with 1st channel + if (channel == 0) { + ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed)); + const bool paused = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ui->btn_pause)); + + if (paused != ui->paused) { + ui->paused = paused; + gtk_widget_queue_draw(ui->darea); + } + } + if (ui->paused) { + return; + } + + uint32_t idx_start; // Display pixel start + uint32_t idx_end; // Display pixel end + int overflow; // Received more audio-data than display-pixel + + // Process this channel's audio-data for display + ScoChan* chn = &ui->chn[channel]; + overflow = process_channel(ui, chn, n_elem, data, &idx_start, &idx_end); + + // Signal gtk's main thread to redraw the widget after the last channel + if ((uint32_t)channel + 1 == ui->n_channels) { + if (overflow > 1) { + // Redraw complete widget + gtk_widget_queue_draw(ui->darea); + } else if (idx_end > idx_start) { + // Redraw area between start -> end pixel + gtk_widget_queue_draw_area(ui->darea, idx_start - 2, 0, 3 + + idx_end - idx_start, + DAHEIGHT * ui->n_channels); + } else if (idx_end < idx_start) { + // Wrap-around: redraw area between 0->start AND end->right-end + gtk_widget_queue_draw_area( + ui->darea, + idx_start - 2, 0, + 3 + DAWIDTH - idx_start, DAHEIGHT * ui->n_channels); + gtk_widget_queue_draw_area( + ui->darea, + 0, 0, + idx_end + 1, DAHEIGHT * ui->n_channels); + } + } +} + +static LV2UI_Handle +instantiate(const LV2UI_Descriptor* descriptor, + const char* plugin_uri, + const char* bundle_path, + LV2UI_Write_Function write_function, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) +{ + EgScopeUI* ui = (EgScopeUI*)malloc(sizeof(EgScopeUI)); + + if (!ui) { + fprintf(stderr, "EgScope.lv2 UI: out of memory\n"); + return NULL; + } + + ui->map = NULL; + *widget = NULL; + + if (!strcmp(plugin_uri, SCO_URI "#Mono")) { + ui->n_channels = 1; + } else if (!strcmp(plugin_uri, SCO_URI "#Stereo")) { + ui->n_channels = 2; + } else { + free(ui); + return NULL; + } + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + ui->map = (LV2_URID_Map*)features[i]->data; + } + } + + if (!ui->map) { + fprintf(stderr, "EgScope.lv2 UI: Host does not support urid:map\n"); + free(ui); + return NULL; + } + + // Initialize private data structure + ui->write = write_function; + ui->controller = controller; + + ui->vbox = NULL; + ui->hbox = NULL; + ui->darea = NULL; + ui->stride = 25; + ui->paused = false; + ui->rate = 48000; + + ui->chn[0].idx = 0; + ui->chn[0].sub = 0; + ui->chn[1].idx = 0; + ui->chn[1].sub = 0; + memset(ui->chn[0].data_min, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[0].data_max, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[1].data_min, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[1].data_max, 0, sizeof(float) * DAWIDTH); + + map_sco_uris(ui->map, &ui->uris); + lv2_atom_forge_init(&ui->forge, ui->map); + + // Setup UI + ui->hbox = gtk_hbox_new(FALSE, 0); + ui->vbox = gtk_vbox_new(FALSE, 0); + + ui->darea = gtk_drawing_area_new(); + gtk_widget_set_size_request(ui->darea, DAWIDTH, DAHEIGHT * ui->n_channels); + + ui->lbl_speed = gtk_label_new("Samples/Pixel"); + ui->lbl_amp = gtk_label_new("Amplitude"); + + ui->sep[0] = gtk_hseparator_new(); + ui->sep[1] = gtk_label_new(""); + ui->btn_pause = gtk_toggle_button_new_with_label("Pause"); + + ui->spb_speed_adj = (GtkAdjustment*)gtk_adjustment_new( + 25.0, 1.0, 1000.0, 1.0, 5.0, 0.0); + ui->spb_speed = gtk_spin_button_new(ui->spb_speed_adj, 1.0, 0); + + ui->spb_amp_adj = (GtkAdjustment*)gtk_adjustment_new( + 1.0, 0.1, 6.0, 0.1, 1.0, 0.0); + ui->spb_amp = gtk_spin_button_new(ui->spb_amp_adj, 0.1, 1); + + gtk_box_pack_start(GTK_BOX(ui->hbox), ui->darea, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ui->hbox), ui->vbox, FALSE, FALSE, 4); + + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_speed, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_speed, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[0], FALSE, FALSE, 8); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_amp, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_amp, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[1], TRUE, FALSE, 8); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->btn_pause, FALSE, FALSE, 2); + + g_signal_connect(G_OBJECT(ui->darea), "expose_event", + G_CALLBACK(on_expose_event), ui); + g_signal_connect(G_OBJECT(ui->spb_amp), "value-changed", + G_CALLBACK(on_cfg_changed), ui); + g_signal_connect(G_OBJECT(ui->spb_speed), "value-changed", + G_CALLBACK(on_cfg_changed), ui); + + *widget = ui->hbox; + + /* Send UIOn message to plugin, which will request state and enable message + transmission. */ + send_ui_enable(ui); + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + EgScopeUI* ui = (EgScopeUI*)handle; + /* Send UIOff message to plugin, which will save state and disable message + * transmission. */ + send_ui_disable(ui); + gtk_widget_destroy(ui->darea); + free(ui); +} + +static int +recv_raw_audio(EgScopeUI* ui, const LV2_Atom_Object* obj) +{ + const LV2_Atom* chan_val = NULL; + const LV2_Atom* data_val = NULL; + const int n_props = lv2_atom_object_get( + obj, + ui->uris.channelID, &chan_val, + ui->uris.audioData, &data_val, + NULL); + + if (n_props != 2 || + chan_val->type != ui->uris.atom_Int || + data_val->type != ui->uris.atom_Vector) { + // Object does not have the required properties with correct types + fprintf(stderr, "eg-scope.lv2 UI error: Corrupt audio message\n"); + return 1; + } + + // Get the values we need from the body of the property value atoms + const int32_t chn = ((const LV2_Atom_Int*)chan_val)->body; + const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*)data_val; + if (vec->body.child_type != ui->uris.atom_Float) { + return 1; // Vector has incorrect element type + } + + // Number of elements = (total size - header size) / element size + const size_t n_elem = ((data_val->size - sizeof(LV2_Atom_Vector_Body)) + / sizeof(float)); + + // Float elements immediately follow the vector body header + const float* data = (const float*)(&vec->body + 1); + + // Update display + update_scope(ui, chn, n_elem, data); + return 0; +} + +static int +recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj) +{ + const LV2_Atom* spp_val = NULL; + const LV2_Atom* amp_val = NULL; + const LV2_Atom* rate_val = NULL; + const int n_props = lv2_atom_object_get( + obj, + ui->uris.ui_spp, &spp_val, + ui->uris.ui_amp, &_val, + ui->uris.param_sampleRate, &rate_val, + NULL); + + if (n_props != 3 || + spp_val->type != ui->uris.atom_Int || + amp_val->type != ui->uris.atom_Float || + rate_val->type != ui->uris.atom_Float) { + // Object does not have the required properties with correct types + fprintf(stderr, "eg-scope.lv2 UI error: Corrupt state message\n"); + return 1; + } + + // Get the values we need from the body of the property value atoms + const int32_t spp = ((const LV2_Atom_Int*)spp_val)->body; + const float amp = ((const LV2_Atom_Float*)amp_val)->body; + const float rate = ((const LV2_Atom_Float*)rate_val)->body; + + // Update UI + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp), amp); + ui->rate = rate; + + return 0; +} + +/** + Receive data from the DSP-backend. + + This is called by the host, typically at a rate of around 25 FPS. + + Ideally this happens regularly and with relatively low latency, but there + are no hard guarantees about message delivery. +*/ +static void +port_event(LV2UI_Handle handle, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + EgScopeUI* ui = (EgScopeUI*)handle; + const LV2_Atom* atom = (const LV2_Atom*)buffer; + + /* Check type of data received + * - format == 0: Control port event (float) + * - format > 0: Message (atom) + */ + if (format == ui->uris.atom_eventTransfer && + atom->type == ui->uris.atom_Blank) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + if (obj->body.otype == ui->uris.RawAudio) { + recv_raw_audio(ui, obj); + } else if (obj->body.otype == ui->uris.ui_State) { + recv_ui_state(ui, obj); + } + } +} + +static const LV2UI_Descriptor descriptor = { + SCO_URI "#ui", + instantiate, + cleanup, + port_event, + NULL +}; + +LV2_SYMBOL_EXPORT +const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg-scope.lv2/manifest.ttl.in b/plugins/eg-scope.lv2/manifest.ttl.in new file mode 100644 index 0000000..a64aff1 --- /dev/null +++ b/plugins/eg-scope.lv2/manifest.ttl.in @@ -0,0 +1,21 @@ +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + +# ==== Mono plugin variant ==== + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + +# ==== Stereo plugin variant ==== + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + +# ==== Gtk 2.0 UI ==== + + a ui:GtkUI ; + ui:binary ; + rdfs:seeAlso . diff --git a/plugins/eg-scope.lv2/uris.h b/plugins/eg-scope.lv2/uris.h new file mode 100644 index 0000000..bd57551 --- /dev/null +++ b/plugins/eg-scope.lv2/uris.h @@ -0,0 +1,73 @@ +/* + Copyright 2013 Robin Gareus + + 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. +*/ + +#ifndef SCO_URIS_H +#define SCO_URIS_H + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#define SCO_URI "http://lv2plug.in/plugins/eg-scope" + +typedef struct { + // URIs defined in LV2 specifications + LV2_URID atom_Blank; + LV2_URID atom_Vector; + LV2_URID atom_Float; + LV2_URID atom_Int; + LV2_URID atom_eventTransfer; + LV2_URID param_sampleRate; + + /* URIs defined for this plugin. It is best to re-use existing URIs as + much as possible, but plugins may need more vocabulary specific to their + needs. These are used as types and properties for plugin:UI + communication, as well as for saving state. */ + LV2_URID RawAudio; + LV2_URID channelID; + LV2_URID audioData; + LV2_URID ui_On; + LV2_URID ui_Off; + LV2_URID ui_State; + LV2_URID ui_spp; + LV2_URID ui_amp; +} ScoLV2URIs; + +static inline void +map_sco_uris(LV2_URID_Map* map, ScoLV2URIs* uris) +{ + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Vector = map->map(map->handle, LV2_ATOM__Vector); + uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); + uris->atom_Int = map->map(map->handle, LV2_ATOM__Int); + uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); + uris->param_sampleRate = map->map(map->handle, LV2_PARAMETERS__sampleRate); + + /* Note the convention that URIs for types are capitalized, and URIs for + everything else (mainly properties) are not, just as in LV2 + specifications. */ + uris->RawAudio = map->map(map->handle, SCO_URI "#RawAudio"); + uris->audioData = map->map(map->handle, SCO_URI "#audioData"); + uris->channelID = map->map(map->handle, SCO_URI "#channelID"); + uris->ui_On = map->map(map->handle, SCO_URI "#UIOn"); + uris->ui_Off = map->map(map->handle, SCO_URI "#UIOff"); + uris->ui_State = map->map(map->handle, SCO_URI "#UIState"); + uris->ui_spp = map->map(map->handle, SCO_URI "#ui-spp"); + uris->ui_amp = map->map(map->handle, SCO_URI "#ui-amp"); +} + +#endif /* SCO_URIS_H */ diff --git a/plugins/eg-scope.lv2/wscript b/plugins/eg-scope.lv2/wscript new file mode 100644 index 0000000..807d15d --- /dev/null +++ b/plugins/eg-scope.lv2/wscript @@ -0,0 +1,73 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-scope.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Scope Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') + + autowaf.check_pkg(conf, 'cairo', uselib_store='CAIRO', + atleast_version='1.8.10', mandatory=True) + autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', + atleast_version='2.18.0', mandatory=False) + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-scope.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + for i in ['manifest.ttl', 'examploscope.ttl']: + bld(features = 'subst', + source = i + '.in', + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'examploscope.c', + name = 'examploscope', + target = '%s/examploscope' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + + # Build UI library + if bld.is_defined('HAVE_GTK2'): + obj = bld(features = 'c cshlib', + source = 'examploscope_ui.c', + name = 'examploscope_ui', + target = '%s/examploscope_ui' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'GTK2 CAIRO LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg01-amp.lv2/README.txt b/plugins/eg01-amp.lv2/README.txt deleted file mode 100644 index 41683d3..0000000 --- a/plugins/eg01-amp.lv2/README.txt +++ /dev/null @@ -1,19 +0,0 @@ -== Simple Amplifier == - -This plugin is a simple example of a basic LV2 plugin with no additional features. -It has audio ports which contain an array of `float`, -and a control port which contains a single `float`. - -LV2 plugins are defined in two parts: code and data. -The code is written in C, or any C compatible language such as C++. -Static data is described separately in the human and machine friendly http://www.w3.org/TeamSubmission/turtle/[Turtle] syntax. - -Generally, the goal is to keep code minimal, -and describe as much as possible in the static data. -There are several advantages to this approach: - - * Hosts can discover and inspect plugins without loading or executing any plugin code. - * Plugin data can be used from a wide range of generic tools like scripting languages and command line utilities. - * The standard data model allows the use of existing vocabularies to describe plugins and related information. - * The language is extensible, so authors may describe any data without requiring changes to the LV2 specification. - * Labels and documentation are translatable, and available to hosts for display in user interfaces. diff --git a/plugins/eg01-amp.lv2/amp.c b/plugins/eg01-amp.lv2/amp.c deleted file mode 100644 index eea9861..0000000 --- a/plugins/eg01-amp.lv2/amp.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - 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 AMP_URI "http://lv2plug.in/plugins/eg-amp" - -/** - In code, ports are referred to by index. An enumeration of port indices - should be defined for readability. -*/ -typedef enum { - AMP_GAIN = 0, - AMP_INPUT = 1, - AMP_OUTPUT = 2 -} 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 - const float* gain; - const float* input; - float* output; -} Amp; - -/** - 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) -{ - Amp* amp = (Amp*)malloc(sizeof(Amp)); - - return (LV2_Handle)amp; -} - -/** - 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) -{ - Amp* amp = (Amp*)instance; - - switch ((PortIndex)port) { - case AMP_GAIN: - amp->gain = (const float*)data; - break; - case AMP_INPUT: - amp->input = (const float*)data; - break; - case AMP_OUTPUT: - amp->output = (float*)data; - break; - } -} - -/** - 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) -{ -} - -/** Define a macro for converting a gain in dB to a coefficient. */ -#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) - -/** - The `run()` method is the main process function of the plugin. It processes - a block of audio in the audio context. Since this plugin is - `lv2:hardRTCapable`, `run()` must be real-time safe, so blocking (e.g. with - a mutex) or memory allocation are not allowed. -*/ -static void -run(LV2_Handle instance, uint32_t n_samples) -{ - const Amp* amp = (const Amp*)instance; - - const float gain = *(amp->gain); - const float* const input = amp->input; - float* const output = amp->output; - - const float coef = DB_CO(gain); - - for (uint32_t pos = 0; pos < n_samples; pos++) { - output[pos] = input[pos] * coef; - } -} - -/** - The `deactivate()` method is the counterpart to `activate()`, and is 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; -} - -/** - Every plugin must define an `LV2_Descriptor`. 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 = { - AMP_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/plugins/eg01-amp.lv2/amp.ttl b/plugins/eg01-amp.lv2/amp.ttl deleted file mode 100644 index f4a87f2..0000000 --- a/plugins/eg01-amp.lv2/amp.ttl +++ /dev/null @@ -1,86 +0,0 @@ -# The full description of the plugin is in this file, which is linked to from -# `manifest.ttl`. This is done so the host only needs to scan the relatively -# small `manifest.ttl` files to quickly discover all plugins. - -@prefix doap: . -@prefix lv2: . -@prefix rdf: . -@prefix rdfs: . - -# First the type of the plugin is described. All plugins must explicitly list -# `lv2:Plugin` as a type. A more specific type should also be given, where -# applicable, so hosts can present a nicer UI for loading plugins. Note that -# this URI is the identifier of the plugin, so if it does not match the one in -# `manifest.ttl`, the host will not discover the plugin data at all. - - a lv2:Plugin , - lv2:AmplifierPlugin ; -# Plugins are associated with a project, where common information like -# developers, home page, and so on are described. This plugin is part of the -# LV2 project, which has URI , and is described -# elsewhere. Typical plugin collections will describe the project in -# manifest.ttl - lv2:project ; -# Every plugin must have a name, described with the doap:name property. -# Translations to various languages can be added by putting a language tag -# after strings as shown. - doap:name "Simple Amplifier" , - "简单放大器"@ch , - "Einfacher Verstärker"@de , - "Simple Amp"@en-gb , - "Amplificador Simple"@es , - "Amplificateur de Base"@fr , - "Amplificatore Semplice"@it , - "簡単なアンプ"@jp , - "Просто Усилитель"@ru ; - doap:license ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ -# Every port must have at least two types, one that specifies direction -# (lv2:InputPort or lv2:OutputPort), and another to describe the data type. -# This port is a lv2:ControlPort, which means it contains a single float. - a lv2:InputPort , - lv2:ControlPort ; - lv2:index 0 ; - lv2:symbol "gain" ; - lv2:name "Gain" , - "收益"@ch , - "Gewinn"@de , - "Gain"@en-gb , - "Aumento"@es , - "Gain"@fr , - "Guadagno"@it , - "利益"@jp , - "Увеличение"@ru ; -# An lv2:ControlPort should always describe its default value, and usually a -# minimum and maximum value. Defining a range is not strictly required, but -# should be done wherever possible to aid host support, particularly for UIs. - lv2:default 0.0 ; - lv2:minimum -90.0 ; - lv2:maximum 24.0 ; - lv2:scalePoint [ - rdfs:label "+5" ; - rdf:value 5.0 - ] , [ - rdfs:label "0" ; - rdf:value 0.0 - ] , [ - rdfs:label "-5" ; - rdf:value -5.0 - ] , [ - rdfs:label "-10" ; - rdf:value -10.0 - ] - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 1 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg01-amp.lv2/manifest.ttl.in b/plugins/eg01-amp.lv2/manifest.ttl.in deleted file mode 100644 index da8a2c4..0000000 --- a/plugins/eg01-amp.lv2/manifest.ttl.in +++ /dev/null @@ -1,101 +0,0 @@ -# ==== Bundles ==== -# -# LV2 plugins are installed in ``bundles'', a directory with a particular -# format. Inside the bundle, the entry point is a file called `manifest.ttl`. -# The manifest lists the plugins (or other resources) that are in this bundle, -# and the files that contain further information. -# -# Hosts typically read the `manifest.ttl` of every bundle when starting up to -# discover what LV2 plugins and other resources are present. Accordingly, -# manifest files should be as small as possible for performance reasons. -# -# -# ==== Namespace Prefixes ==== -# -# Turtle files contain many URIs. To make this more readable, prefixes can be -# defined. For example, with the `lv2:` prefix below, instead of -# the shorter form `lv2:Plugin` can be -# used. This is just a shorthand for URIs within one file, the prefixes are -# not significant otherwise. - -@prefix lv2: . -@prefix rdfs: . - -# ==== A Plugin Entry ==== - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - -# The token `@LIB_EXT@` above is replaced by the build system with the -# appropriate extension for the current platform (e.g. .so, .dylib, .dll). -# This file is called called `manifest.ttl.in` rather than `manifest.ttl` -# to indicate that it is not the final file to be installed. -# This is not necessary, but is a good idea for portable plugins. -# For reability, the following text will assume `.so` is the extension used. -# -# In short, this declares that the resource with URI -# `http://lv2plug.in/plugins/eg-amp` is an LV2 plugin, with executable code in -# the file `amp.so` and a full description in `amp.ttl`. These paths are -# relative to the bundle directory. -# -# There are 3 statements in this description: -# [options="header"] -# |================================================================ -# | Subject | Predicate | Object -# | | a | lv2:Plugin -# | | lv2:binary | -# | | rdfs:seeAlso | -# |================================================================ -# -# The semicolon is used to continue the previous subject; an equivalent -# but more verbose syntax for the same data is: - - a lv2:Plugin . - lv2:binary . - rdfs:seeAlso . - -# (Since this data is equivalent, it is safe, if pointless, to list it twice) -# -# The documentation for a URI can often be found by visiting that URI in a web -# browser, e.g. the documentation for lv2:binary can be found at -# . All standard LV2 classes and -# properties are documented in this way, so if you encounter a URI in some data -# which you do not understand, try this first. -# -# The URI of a plugin does not need to be a resolvable web address, it just -# serves as a global identifier. However, it is a good idea to use an actual -# web address if possible for easy access documentation, downloads, and so on, -# even if no documents are currently hosted there. There are compatibility -# rules about when the URI of a plugin must be changed, see the -# http://lv2plug.in/ns/lv2core[LV2 specification] for details. Note that this -# does not require authors to control a top-level domain; for example, URIs in -# project directories at shared hosting sites are fine. It is not required to -# use HTTP URIs, but use of other schemes is strongly discouraged. -# -# AUTHORS MUST NOT CREATE URIS AT DOMAINS THEY DO NOT CONTROL WITHOUT -# PERMISSION, AND *ESPECIALLY* MUST NOT CREATE INVALID URIS, E.G. WHERE THE -# PORTION FOLLOWING ``http://'' IS NOT A DOMAIN NAME. If you need an example -# URI, the domain http://example.org/ is reserved for this purpose. -# -# A detailed explanation of each statement follows. - - a lv2:Plugin . - -# The `a`, as in ``is a'', is a Turtle shortcut for `rdf:type`. -# `lv2:Plugin` expands to (using the -# `lv2:` prefix above) which is the type of all LV2 plugins. -# This statement means `` is an LV2 plugin''. - - lv2:binary . - -# This says ```eg-amp` has executable code in the file `amp.so`''. -# Relative URIs in manifest files are relative to the bundle directory, so this -# refers to the file amp.so in the same directory as this manifest.ttl file. - - rdfs:seeAlso . - -# This says ``there is more information about `eg-amp` in the file `amp.ttl`''. -# The host will look at all such files when it needs to actually use or -# investigate the plugin. diff --git a/plugins/eg01-amp.lv2/waf b/plugins/eg01-amp.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg01-amp.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg01-amp.lv2/wscript b/plugins/eg01-amp.lv2/wscript deleted file mode 100644 index d4295ff..0000000 --- a/plugins/eg01-amp.lv2/wscript +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-amp.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Amp Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') - - conf.check(features='c cprogram', lib='m', uselib_store='M', mandatory=False) - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-amp.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = '%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Copy other data files to build bundle (build/eg-amp.lv2) - for i in ['amp.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = '%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Use LV2 headers from parent directory if building as a sub-project - includes = None - if autowaf.is_child: - includes = '../..' - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'amp.c', - name = 'amp', - target = '%s/amp' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - uselib = 'M LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat - diff --git a/plugins/eg02-midigate.lv2/README.txt b/plugins/eg02-midigate.lv2/README.txt deleted file mode 100644 index 8f4a0f0..0000000 --- a/plugins/eg02-midigate.lv2/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -== MIDI Gate == - -This plugin demonstrates: - - * Receiving MIDI input - - * Processing audio based on MIDI events with sample accuracy - - * Supporting MIDI programs which the host can control/automate, or present a - user interface for with human readable labels diff --git a/plugins/eg02-midigate.lv2/manifest.ttl.in b/plugins/eg02-midigate.lv2/manifest.ttl.in deleted file mode 100644 index d32f1dc..0000000 --- a/plugins/eg02-midigate.lv2/manifest.ttl.in +++ /dev/null @@ -1,10 +0,0 @@ -# The manifest.ttl file follows the same template as the previous example. - -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg02-midigate.lv2/midigate.c b/plugins/eg02-midigate.lv2/midigate.c deleted file mode 100644 index 8d2bd74..0000000 --- a/plugins/eg02-midigate.lv2/midigate.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - Copyright 2013 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. -*/ - -#include - -#include -#include - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" - -typedef enum { - MIDIGATE_CONTROL = 0, - MIDIGATE_IN = 1, - MIDIGATE_OUT = 2 -} PortIndex; - -typedef struct { - // Port buffers - const LV2_Atom_Sequence* control; - const float* in; - float* out; - - // Features - LV2_URID_Map* map; - - struct { - LV2_URID midi_MidiEvent; - } uris; - - unsigned n_active_notes; - unsigned program; // 0 = normal, 1 = inverted -} Midigate; - -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature* const* features) -{ - /** Scan features array for the URID feature we need. */ - LV2_URID_Map* map = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID__map)) { - map = (LV2_URID_Map*)features[i]->data; - break; - } - } - if (!map) { - /** - No URID feature given. This is a host bug since we require this - feature, but should be handled gracefully anyway. - */ - return NULL; - } - - Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); - self->map = map; - self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); - - return (LV2_Handle)self; -} - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Midigate* self = (Midigate*)instance; - - switch ((PortIndex)port) { - case MIDIGATE_CONTROL: - self->control = (const LV2_Atom_Sequence*)data; - break; - case MIDIGATE_IN: - self->in = (const float*)data; - break; - case MIDIGATE_OUT: - self->out = (float*)data; - break; - } -} - -static void -activate(LV2_Handle instance) -{ - Midigate* self = (Midigate*)instance; - self->n_active_notes = 0; - self->program = 0; -} - -/** - A function to write a chunk of output, to be called from run(). If the gate - is high, then the input will be passed through for this chunk, otherwise - silence is written. -*/ -static void -write_output(Midigate* self, uint32_t offset, uint32_t len) -{ - const bool active = (self->program == 0) - ? (self->n_active_notes > 0) - : (self->n_active_notes == 0); - if (active) { - memcpy(self->out + offset, self->in + offset, len * sizeof(float)); - } else { - memset(self->out + offset, 0, len * sizeof(float)); - } -} - -/** - This plugin works through the cycle in chunks starting at offset zero. The - +offset+ represents the current time within this this cycle, so - the output from 0 to +offset+ has already been written. - - MIDI events are read in a loop. In each iteration, the number of active - notes (on note on and note off) or the program (on program change) is - updated, then the output is written up until the current event time. Then - +offset+ is updated and the next event is processed. After the loop the - final chunk from the last event to the end of the cycle is emitted. - - There is currently no standard way to describe MIDI programs in LV2, so the - host has no way of knowing that these programs exist and should be presented - to the user. A future version of LV2 will address this shortcoming. - - This pattern of iterating over input events and writing output along the way - is a common idiom for writing sample accurate output based on event input. - - Note that this simple example simply writes input or zero for each sample - based on the gate. A serious implementation would need to envelope the - transition to avoid aliasing. -*/ -static void -run(LV2_Handle instance, uint32_t sample_count) -{ - Midigate* self = (Midigate*)instance; - uint32_t offset = 0; - - LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) { - if (ev->body.type == self->uris.midi_MidiEvent) { - const uint8_t* const msg = (const uint8_t*)(ev + 1); - switch (lv2_midi_message_type(msg)) { - case LV2_MIDI_MSG_NOTE_ON: - ++self->n_active_notes; - break; - case LV2_MIDI_MSG_NOTE_OFF: - --self->n_active_notes; - break; - case LV2_MIDI_MSG_PGM_CHANGE: - if (msg[1] == 0 || msg[1] == 1) { - self->program = msg[1]; - } - break; - default: break; - } - } - - write_output(self, offset, ev->time.frames - offset); - offset = (uint32_t)ev->time.frames; - } - - write_output(self, offset, sample_count - offset); -} - -/** - We have no resources to free on deactivation. - Note that the next call to activate will re-initialise the state, namely - self->n_active_notes, so there is no need to do so here. -*/ -static void -deactivate(LV2_Handle instance) -{} - -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -/** - This plugin also has no extension data to return. -*/ -static const void* -extension_data(const char* uri) -{ - return NULL; -} - -static const LV2_Descriptor descriptor = { - MIDIGATE_URI, - instantiate, - connect_port, - activate, - run, - deactivate, - cleanup, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg02-midigate.lv2/midigate.ttl b/plugins/eg02-midigate.lv2/midigate.ttl deleted file mode 100644 index e14a329..0000000 --- a/plugins/eg02-midigate.lv2/midigate.ttl +++ /dev/null @@ -1,56 +0,0 @@ -# The same set of namespace prefixes with two additions for LV2 extensions this -# plugin uses: atom and urid. - -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix midi: . -@prefix rdfs: . -@prefix urid: . - - - a lv2:Plugin ; - doap:name "Example MIDI Gate" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; -# This plugin has three ports. There is an audio input and output as before, -# as well as a new AtomPort. An AtomPort buffer contains an Atom, which is a -# generic container for any type of data. In this case, we want to receive -# MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is -# a series of events with time stamps. -# -# Events themselves are also generic and can contain any type of data, but in -# this case we are only interested in MIDI events. The (optional) -# +atom:supports+ property describes which event types are supported. Though -# not required, this information should always be given so the host knows what -# types of event it can expect the plugin to understand. -# -# The (optional) +lv2:designation+ of this port is +lv2:control+, which -# indicates that this is the "main" control port where the host should send -# events it expects to configure the plugin, in this case changing the MIDI -# program. This is necessary since it is possible to have several MIDI input -# ports, though typically it is best to have one. - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports midi:MidiEvent ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 1 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg02-midigate.lv2/waf b/plugins/eg02-midigate.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg02-midigate.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg02-midigate.lv2/wscript b/plugins/eg02-midigate.lv2/wscript deleted file mode 100644 index 44336af..0000000 --- a/plugins/eg02-midigate.lv2/wscript +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-midigate.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Midigate Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-midigate.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = '%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Copy other data files to build bundle (build/eg-midigate.lv2) - for i in ['midigate.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = '%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Use LV2 headers from parent directory if building as a sub-project - includes = None - if autowaf.is_child: - includes = '../..' - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'midigate.c', - name = 'midigate', - target = '%s/midigate' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - uselib = 'LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat - diff --git a/plugins/eg03-metro.lv2/README.txt b/plugins/eg03-metro.lv2/README.txt deleted file mode 100644 index 5e9a84a..0000000 --- a/plugins/eg03-metro.lv2/README.txt +++ /dev/null @@ -1,9 +0,0 @@ -== Metronome == - -This plugin demonstrates tempo synchronisation by clicking on every beat. The -host sends this information to the plugin as events, so an event with new time -and tempo information will be received whenever there is a change. - -Time is assumed to continue rolling at the tempo and speed defined by the last -received tempo event, even across cycles, until a new tempo event is received -or the plugin is deactivated. diff --git a/plugins/eg03-metro.lv2/manifest.ttl.in b/plugins/eg03-metro.lv2/manifest.ttl.in deleted file mode 100644 index bd93f66..0000000 --- a/plugins/eg03-metro.lv2/manifest.ttl.in +++ /dev/null @@ -1,7 +0,0 @@ -@prefix lv2: . -@prefix rdfs: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg03-metro.lv2/metro.c b/plugins/eg03-metro.lv2/metro.c deleted file mode 100644 index 5e484b7..0000000 --- a/plugins/eg03-metro.lv2/metro.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - LV2 Metronome Example Plugin - Copyright 2012 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. -*/ - -#include -#include -#include -#include -#ifndef __cplusplus -# include -#endif - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/time/time.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#ifndef M_PI -# define M_PI 3.14159265 -#endif - -#define EG_METRO_URI "http://lv2plug.in/plugins/eg-metro" - -typedef struct { - LV2_URID atom_Blank; - LV2_URID atom_Float; - LV2_URID atom_Object; - LV2_URID atom_Path; - LV2_URID atom_Resource; - LV2_URID atom_Sequence; - LV2_URID time_Position; - LV2_URID time_barBeat; - LV2_URID time_beatsPerMinute; - LV2_URID time_speed; -} MetroURIs; - -static const double attack_s = 0.005; -static const double decay_s = 0.075; - -enum { - METRO_CONTROL = 0, - METRO_OUT = 1 -}; - -/** During execution this plugin can be in one of 3 states: */ -typedef enum { - STATE_ATTACK, // Envelope rising - STATE_DECAY, // Envelope lowering - STATE_OFF // Silent -} State; - -/** - This plugin must keep track of more state than previous examples to be able - to render audio. The basic idea is to generate a single cycle of a sine - wave which is conceptually played continuously. The 'tick' is generated by - enveloping the amplitude so there is a short attack/decay peak around a - tick, and silence the rest of the time. - - This example uses a simple AD envelope with fixed parameters. A more - sophisticated implementation might use a more advanced envelope and allow - the user to modify these parameters, the frequency of the wave, and so on. - */ -typedef struct { - LV2_URID_Map* map; // URID map feature - MetroURIs uris; // Cache of mapped URIDs - - struct { - LV2_Atom_Sequence* control; - float* output; - } ports; - - // Variables to keep track of the tempo information sent by the host - double rate; // Sample rate - float bpm; // Beats per minute (tempo) - float speed; // Transport speed (usually 0=stop, 1=play) - - uint32_t elapsed_len; // Frames since the start of the last click - uint32_t wave_offset; // Current play offset in the wave - State state; // Current play state - - // One cycle of a sine wave - float* wave; - uint32_t wave_len; - - // Envelope parameters - uint32_t attack_len; - uint32_t decay_len; -} Metro; - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Metro* self = (Metro*)instance; - - switch (port) { - case METRO_CONTROL: - self->ports.control = (LV2_Atom_Sequence*)data; - break; - case METRO_OUT: - self->ports.output = (float*)data; - break; - default: - break; - } -} - -/** - The activate() method resets the state completely, so the wave offset is - zero and the envelope is off. -*/ -static void -activate(LV2_Handle instance) -{ - Metro* self = (Metro*)instance; - - self->elapsed_len = 0; - self->wave_offset = 0; - self->state = STATE_OFF; -} - -/** - This plugin does a bit more work in instantiate() than the previous - examples. The tempo updates from the host contain several URIs, so those - are mapped, and the sine wave to be played needs to be generated based on - the current sample rate. -*/ -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* path, - const LV2_Feature* const* features) -{ - Metro* self = (Metro*)calloc(1, sizeof(Metro)); - if (!self) { - return NULL; - } - - // Scan host features for URID map - LV2_URID_Map* map = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - map = (LV2_URID_Map*)features[i]->data; - } - } - if (!map) { - fprintf(stderr, "Host does not support urid:map.\n"); - free(self); - return NULL; - } - - // Map URIS - MetroURIs* const uris = &self->uris; - self->map = map; - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); - uris->atom_Object = map->map(map->handle, LV2_ATOM__Object); - uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); - uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); - uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); - uris->time_Position = map->map(map->handle, LV2_TIME__Position); - uris->time_barBeat = map->map(map->handle, LV2_TIME__barBeat); - uris->time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute); - uris->time_speed = map->map(map->handle, LV2_TIME__speed); - - // Initialise instance fields - self->rate = rate; - self->bpm = 120.0f; - self->attack_len = (uint32_t)(attack_s * rate); - self->decay_len = (uint32_t)(decay_s * rate); - self->state = STATE_OFF; - - // Generate one cycle of a sine wave at the desired frequency - const double freq = 440.0 * 2.0; - const double amp = 0.5; - self->wave_len = (uint32_t)(rate / freq); - self->wave = (float*)malloc(self->wave_len * sizeof(float)); - for (uint32_t i = 0; i < self->wave_len; ++i) { - self->wave[i] = (float)(sin(i * 2 * M_PI * freq / rate) * amp); - } - - return (LV2_Handle)self; -} - -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -/** - Play back audio for the range [begin..end) relative to this cycle. This is - called by run() in-between events to output audio up until the current time. -*/ -static void -play(Metro* self, uint32_t begin, uint32_t end) -{ - float* const output = self->ports.output; - const uint32_t frames_per_beat = 60.0f / self->bpm * self->rate; - - if (self->speed == 0.0f) { - memset(output, 0, (end - begin) * sizeof(float)); - return; - } - - for (uint32_t i = begin; i < end; ++i) { - switch (self->state) { - case STATE_ATTACK: - // Amplitude increases from 0..1 until attack_len - output[i] = self->wave[self->wave_offset] * - self->elapsed_len / (float)self->attack_len; - if (self->elapsed_len >= self->attack_len) { - self->state = STATE_DECAY; - } - break; - case STATE_DECAY: - // Amplitude decreases from 1..0 until attack_len + decay_len - output[i] = 0.0f; - output[i] = self->wave[self->wave_offset] * - (1 - ((self->elapsed_len - self->attack_len) / - (float)self->decay_len)); - if (self->elapsed_len >= self->attack_len + self->decay_len) { - self->state = STATE_OFF; - } - break; - case STATE_OFF: - output[i] = 0.0f; - } - - // We continuously play the sine wave regardless of envelope - self->wave_offset = (self->wave_offset + 1) % self->wave_len; - - // Update elapsed time and start attack if necessary - if (++self->elapsed_len == frames_per_beat) { - self->state = STATE_ATTACK; - self->elapsed_len = 0; - } - } -} - -/** - Update the current position based on a host message. This is called by - run() when a time:Position is received. -*/ -static void -update_position(Metro* self, const LV2_Atom_Object* obj) -{ - const MetroURIs* uris = &self->uris; - - // Received new transport position/speed - LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; - lv2_atom_object_get(obj, - uris->time_barBeat, &beat, - uris->time_beatsPerMinute, &bpm, - uris->time_speed, &speed, - NULL); - if (bpm && bpm->type == uris->atom_Float) { - // Tempo changed, update BPM - self->bpm = ((LV2_Atom_Float*)bpm)->body; - } - if (speed && speed->type == uris->atom_Float) { - // Speed changed, e.g. 0 (stop) to 1 (play) - self->speed = ((LV2_Atom_Float*)speed)->body; - } - if (beat && beat->type == uris->atom_Float) { - // Received a beat position, synchronise - // This hard sync may cause clicks, a real plugin would be more graceful - const float frames_per_beat = 60.0f / self->bpm * self->rate; - const float bar_beats = ((LV2_Atom_Float*)beat)->body; - const float beat_beats = bar_beats - floorf(bar_beats); - self->elapsed_len = beat_beats * frames_per_beat; - if (self->elapsed_len < self->attack_len) { - self->state = STATE_ATTACK; - } else if (self->elapsed_len < self->attack_len + self->decay_len) { - self->state = STATE_DECAY; - } else { - self->state = STATE_OFF; - } - } -} - -static void -run(LV2_Handle instance, uint32_t sample_count) -{ - Metro* self = (Metro*)instance; - const MetroURIs* uris = &self->uris; - - // Work forwards in time frame by frame, handling events as we go - const LV2_Atom_Sequence* in = self->ports.control; - uint32_t last_t = 0; - for (const LV2_Atom_Event* ev = lv2_atom_sequence_begin(&in->body); - !lv2_atom_sequence_is_end(&in->body, in->atom.size, ev); - ev = lv2_atom_sequence_next(ev)) { - - // Play the click for the time slice from last_t until now - play(self, last_t, ev->time.frames); - - // Check if this event is an Object - // (or deprecated Blank to tolerate old hosts) - if (ev->body.type == uris->atom_Object || - ev->body.type == uris->atom_Blank) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; - if (obj->body.otype == uris->time_Position) { - // Received position information, update - update_position(self, obj); - } - } - - // Update time for next iteration and move to next event - last_t = ev->time.frames; - } - - // Play for remainder of cycle - play(self, last_t, sample_count); -} - -static const LV2_Descriptor descriptor = { - EG_METRO_URI, - instantiate, - connect_port, - activate, - run, - NULL, // deactivate, - cleanup, - NULL, // extension_data -}; - -LV2_SYMBOL_EXPORT const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg03-metro.lv2/metro.ttl b/plugins/eg03-metro.lv2/metro.ttl deleted file mode 100644 index 8b4af3d..0000000 --- a/plugins/eg03-metro.lv2/metro.ttl +++ /dev/null @@ -1,30 +0,0 @@ -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix time: . -@prefix urid: . - - - a lv2:Plugin ; - doap:name "Example Metronome" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; -# Since this port supports time:Position, the host knows to deliver time and -# tempo information - atom:supports time:Position ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" ; - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 1 ; - lv2:symbol "out" ; - lv2:name "Out" ; - ] . diff --git a/plugins/eg03-metro.lv2/waf b/plugins/eg03-metro.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg03-metro.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg03-metro.lv2/wscript b/plugins/eg03-metro.lv2/wscript deleted file mode 100644 index 40642b6..0000000 --- a/plugins/eg03-metro.lv2/wscript +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-metro.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Metro Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', atleast_version='0.2.0', uselib_store='LV2') - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-metro.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = '%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Copy other data files to build bundle (build/eg-metro.lv2) - bld(features = 'subst', - is_copy = True, - source = 'metro.ttl', - target = '%s/metro.ttl' % bundle, - install_path = '${LV2DIR}/%s' % bundle) - - # Use LV2 headers from parent directory if building as a sub-project - includes = ['.'] - if autowaf.is_child: - includes += ['../..'] - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'metro.c', - name = 'metro', - target = '%s/metro' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat - diff --git a/plugins/eg04-sampler.lv2/README.txt b/plugins/eg04-sampler.lv2/README.txt deleted file mode 100644 index 4eed9e6..0000000 --- a/plugins/eg04-sampler.lv2/README.txt +++ /dev/null @@ -1,13 +0,0 @@ -== Sampler == - -This plugin loads a single sample from a .wav file and plays it back when a MIDI -note on is received. Any sample on the system can be loaded via another event. -A Gtk UI is included which does this, but the host can as well. - -This plugin illustrates: - -- UI <==> Plugin communication via events -- Use of the worker extension for non-realtime tasks (sample loading) -- Use of the log extension to print log messages via the host -- Saving plugin state via the state extension -- Dynamic plugin control via the same properties saved to state diff --git a/plugins/eg04-sampler.lv2/click.wav b/plugins/eg04-sampler.lv2/click.wav deleted file mode 100644 index 520a18c..0000000 Binary files a/plugins/eg04-sampler.lv2/click.wav and /dev/null differ diff --git a/plugins/eg04-sampler.lv2/manifest.ttl.in b/plugins/eg04-sampler.lv2/manifest.ttl.in deleted file mode 100644 index 8a01428..0000000 --- a/plugins/eg04-sampler.lv2/manifest.ttl.in +++ /dev/null @@ -1,19 +0,0 @@ -# Unlike the previous examples, this manifest lists more than one resource: the -# plugin as usual, and the UI. The descriptions are similar, but have -# different types, so the host can decide from this file alone whether or not -# it is interested, and avoid following the `rdfs:seeAlso` link if not (though -# in this case both are described in the same file). - -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - - - a ui:GtkUI ; - ui:binary ; - rdfs:seeAlso . diff --git a/plugins/eg04-sampler.lv2/sampler.c b/plugins/eg04-sampler.lv2/sampler.c deleted file mode 100644 index 54da799..0000000 --- a/plugins/eg04-sampler.lv2/sampler.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard - Copyright 2011 Gabriel M. Beddingfield - Copyright 2011 James Morris - - 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 -#include -#include -#ifndef __cplusplus -# include -#endif - -#include - -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/log/logger.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/ext/worker/worker.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#include "./uris.h" - -enum { - SAMPLER_CONTROL = 0, - SAMPLER_NOTIFY = 1, - SAMPLER_OUT = 2 -}; - -static const char* default_sample_file = "click.wav"; - -typedef struct { - SF_INFO info; // Info about sample from sndfile - float* data; // Sample data in float - char* path; // Path of file - uint32_t path_len; // Length of path -} Sample; - -typedef struct { - // Features - LV2_URID_Map* map; - LV2_Worker_Schedule* schedule; - LV2_Log_Log* log; - - // Forge for creating atoms - LV2_Atom_Forge forge; - - // Logger convenience API - LV2_Log_Logger logger; - - // Sample - Sample* sample; - - // Ports - const LV2_Atom_Sequence* control_port; - LV2_Atom_Sequence* notify_port; - float* output_port; - - // Forge frame for notify port (for writing worker replies) - LV2_Atom_Forge_Frame notify_frame; - - // URIs - SamplerURIs uris; - - // Current position in run() - uint32_t frame_offset; - - // Playback state - sf_count_t frame; - bool play; -} Sampler; - -/** - An atom-like message used internally to apply/free samples. - - This is only used internally to communicate with the worker, it is never - sent to the outside world via a port since it is not POD. It is convenient - to use an Atom header so actual atoms can be easily sent through the same - ringbuffer. -*/ -typedef struct { - LV2_Atom atom; - Sample* sample; -} SampleMessage; - -/** - Load a new sample and return it. - - Since this is of course not a real-time safe action, this is called in the - worker thread only. The sample is loaded and returned only, plugin state is - not modified. -*/ -static Sample* -load_sample(Sampler* self, const char* path) -{ - const size_t path_len = strlen(path); - - lv2_log_trace(&self->logger, "Loading sample %s\n", path); - - Sample* const sample = (Sample*)malloc(sizeof(Sample)); - SF_INFO* const info = &sample->info; - SNDFILE* const sndfile = sf_open(path, SFM_READ, info); - - if (!sndfile || !info->frames || (info->channels != 1)) { - lv2_log_error(&self->logger, "Failed to open sample '%s'\n", path); - free(sample); - return NULL; - } - - // Read data - float* const data = malloc(sizeof(float) * info->frames); - if (!data) { - lv2_log_error(&self->logger, "Failed to allocate memory for sample\n"); - return NULL; - } - sf_seek(sndfile, 0ul, SEEK_SET); - sf_read_float(sndfile, data, info->frames); - sf_close(sndfile); - - // Fill sample struct and return it - sample->data = data; - sample->path = (char*)malloc(path_len + 1); - sample->path_len = (uint32_t)path_len; - memcpy(sample->path, path, path_len + 1); - - return sample; -} - -static void -free_sample(Sampler* self, Sample* sample) -{ - if (sample) { - lv2_log_trace(&self->logger, "Freeing %s\n", sample->path); - free(sample->path); - free(sample->data); - free(sample); - } -} - -/** - Do work in a non-realtime thread. - - This is called for every piece of work scheduled in the audio thread using - self->schedule->schedule_work(). A reply can be sent back to the audio - thread using the provided respond function. -*/ -static LV2_Worker_Status -work(LV2_Handle instance, - LV2_Worker_Respond_Function respond, - LV2_Worker_Respond_Handle handle, - uint32_t size, - const void* data) -{ - Sampler* self = (Sampler*)instance; - const LV2_Atom* atom = (const LV2_Atom*)data; - if (atom->type == self->uris.eg_freeSample) { - // Free old sample - const SampleMessage* msg = (const SampleMessage*)data; - free_sample(self, msg->sample); - } else { - // Handle set message (load sample). - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data; - - // Get file path from message - const LV2_Atom* file_path = read_set_file(&self->uris, obj); - if (!file_path) { - return LV2_WORKER_ERR_UNKNOWN; - } - - // Load sample. - Sample* sample = load_sample(self, LV2_ATOM_BODY_CONST(file_path)); - if (sample) { - // Loaded sample, send it to run() to be applied. - respond(handle, sizeof(sample), &sample); - } - } - - return LV2_WORKER_SUCCESS; -} - -/** - Handle a response from work() in the audio thread. - - When running normally, this will be called by the host after run(). When - freewheeling, this will be called immediately at the point the work was - scheduled. -*/ -static LV2_Worker_Status -work_response(LV2_Handle instance, - uint32_t size, - const void* data) -{ - Sampler* self = (Sampler*)instance; - - SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample }, - self->sample }; - - // Send a message to the worker to free the current sample - self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); - - // Install the new sample - self->sample = *(Sample*const*)data; - - // Send a notification that we're using a new sample. - lv2_atom_forge_frame_time(&self->forge, self->frame_offset); - write_set_file(&self->forge, &self->uris, - self->sample->path, - self->sample->path_len); - - return LV2_WORKER_SUCCESS; -} - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Sampler* self = (Sampler*)instance; - switch (port) { - case SAMPLER_CONTROL: - self->control_port = (const LV2_Atom_Sequence*)data; - break; - case SAMPLER_NOTIFY: - self->notify_port = (LV2_Atom_Sequence*)data; - break; - case SAMPLER_OUT: - self->output_port = (float*)data; - break; - default: - break; - } -} - -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* path, - const LV2_Feature* const* features) -{ - // Allocate and initialise instance structure. - Sampler* self = (Sampler*)malloc(sizeof(Sampler)); - if (!self) { - return NULL; - } - memset(self, 0, sizeof(Sampler)); - - // Get host features - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID__map)) { - self->map = (LV2_URID_Map*)features[i]->data; - } else if (!strcmp(features[i]->URI, LV2_WORKER__schedule)) { - self->schedule = (LV2_Worker_Schedule*)features[i]->data; - } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { - self->log = (LV2_Log_Log*)features[i]->data; - } - } - if (!self->map) { - lv2_log_error(&self->logger, "Missing feature urid:map\n"); - goto fail; - } else if (!self->schedule) { - lv2_log_error(&self->logger, "Missing feature work:schedule\n"); - goto fail; - } - - // Map URIs and initialise forge/logger - map_sampler_uris(self->map, &self->uris); - lv2_atom_forge_init(&self->forge, self->map); - lv2_log_logger_init(&self->logger, self->map, self->log); - - // Load the default sample file - const size_t path_len = strlen(path); - const size_t file_len = strlen(default_sample_file); - const size_t len = path_len + file_len; - char* sample_path = (char*)malloc(len + 1); - snprintf(sample_path, len + 1, "%s%s", path, default_sample_file); - self->sample = load_sample(self, sample_path); - free(sample_path); - - return (LV2_Handle)self; - -fail: - free(self); - return 0; -} - -static void -cleanup(LV2_Handle instance) -{ - Sampler* self = (Sampler*)instance; - free_sample(self, self->sample); - free(self); -} - -static void -run(LV2_Handle instance, - uint32_t sample_count) -{ - Sampler* self = (Sampler*)instance; - SamplerURIs* uris = &self->uris; - sf_count_t start_frame = 0; - sf_count_t pos = 0; - float* output = self->output_port; - - // Set up forge to write directly to notify output port. - const uint32_t notify_capacity = self->notify_port->atom.size; - lv2_atom_forge_set_buffer(&self->forge, - (uint8_t*)self->notify_port, - notify_capacity); - - // Start a sequence in the notify output port. - lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0); - - // Read incoming events - LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { - self->frame_offset = ev->time.frames; - if (ev->body.type == uris->midi_Event) { - const uint8_t* const msg = (const uint8_t*)(ev + 1); - switch (lv2_midi_message_type(msg)) { - case LV2_MIDI_MSG_NOTE_ON: - start_frame = ev->time.frames; - self->frame = 0; - self->play = true; - break; - default: - break; - } - } else if (lv2_atom_forge_is_object_type(&self->forge, ev->body.type)) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; - if (obj->body.otype == uris->patch_Set) { - // Received a set message, send it to the worker. - lv2_log_trace(&self->logger, "Queueing set message\n"); - self->schedule->schedule_work(self->schedule->handle, - lv2_atom_total_size(&ev->body), - &ev->body); - } else { - lv2_log_trace(&self->logger, - "Unknown object type %d\n", obj->body.otype); - } - } else { - lv2_log_trace(&self->logger, - "Unknown event type %d\n", ev->body.type); - } - } - - // Render the sample (possibly already in progress) - if (self->play) { - uint32_t f = self->frame; - const uint32_t lf = self->sample->info.frames; - - for (pos = 0; pos < start_frame; ++pos) { - output[pos] = 0; - } - - for (; pos < sample_count && f < lf; ++pos, ++f) { - output[pos] = self->sample->data[f]; - } - - self->frame = f; - - if (f == lf) { - self->play = false; - } - } - - // Add zeros to end if sample not long enough (or not playing) - for (; pos < sample_count; ++pos) { - output[pos] = 0.0f; - } -} - -static LV2_State_Status -save(LV2_Handle instance, - LV2_State_Store_Function store, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -{ - Sampler* self = (Sampler*)instance; - if (!self->sample) { - return LV2_STATE_SUCCESS; - } - - LV2_State_Map_Path* map_path = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_STATE__mapPath)) { - map_path = (LV2_State_Map_Path*)features[i]->data; - } - } - - char* apath = map_path->abstract_path(map_path->handle, self->sample->path); - - store(handle, - self->uris.eg_sample, - apath, - strlen(self->sample->path) + 1, - self->uris.atom_Path, - LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); - - free(apath); - - return LV2_STATE_SUCCESS; -} - -static LV2_State_Status -restore(LV2_Handle instance, - LV2_State_Retrieve_Function retrieve, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -{ - Sampler* self = (Sampler*)instance; - - size_t size; - uint32_t type; - uint32_t valflags; - - const void* value = retrieve( - handle, - self->uris.eg_sample, - &size, &type, &valflags); - - if (value) { - const char* path = (const char*)value; - lv2_log_trace(&self->logger, "Restoring file %s\n", path); - free_sample(self, self->sample); - self->sample = load_sample(self, path); - } - - return LV2_STATE_SUCCESS; -} - -static const void* -extension_data(const char* uri) -{ - static const LV2_State_Interface state = { save, restore }; - static const LV2_Worker_Interface worker = { work, work_response, NULL }; - if (!strcmp(uri, LV2_STATE__interface)) { - return &state; - } else if (!strcmp(uri, LV2_WORKER__interface)) { - return &worker; - } - return NULL; -} - -static const LV2_Descriptor descriptor = { - EG_SAMPLER_URI, - instantiate, - connect_port, - NULL, // activate, - run, - NULL, // deactivate, - cleanup, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2_Descriptor* lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg04-sampler.lv2/sampler.ttl b/plugins/eg04-sampler.lv2/sampler.ttl deleted file mode 100644 index 2a89dd2..0000000 --- a/plugins/eg04-sampler.lv2/sampler.ttl +++ /dev/null @@ -1,67 +0,0 @@ -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix patch: . -@prefix rdfs: . -@prefix state: . -@prefix ui: . -@prefix urid: . -@prefix work: . - - - a lv2:Parameter ; - rdfs:label "sample" ; - rdfs:range atom:Path . - - - a lv2:Plugin ; - doap:name "Example Sampler" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map , - work:schedule ; - lv2:optionalFeature lv2:hardRTCapable , - state:loadDefaultState ; - lv2:extensionData state:interface , - work:interface ; - ui:ui ; - patch:writable ; - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports , - patch:Message ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a lv2:OutputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports patch:Message ; - lv2:designation lv2:control ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] ; - state:state [ - - ] . - - - a ui:GtkUI ; - lv2:requiredFeature urid:map ; - lv2:extensionData ui:showInterface ; - ui:portNotification [ - ui:plugin ; - lv2:symbol "notify" ; - ui:notifyType atom:Blank - ] . diff --git a/plugins/eg04-sampler.lv2/sampler_ui.c b/plugins/eg04-sampler.lv2/sampler_ui.c deleted file mode 100644 index d691c98..0000000 --- a/plugins/eg04-sampler.lv2/sampler_ui.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - LV2 Sampler Example Plugin UI - Copyright 2011-2012 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. -*/ - -#include - -#include - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" - -#include "./uris.h" - -#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" - -typedef struct { - LV2_Atom_Forge forge; - - LV2_URID_Map* map; - SamplerURIs uris; - - LV2UI_Write_Function write; - LV2UI_Controller controller; - - GtkWidget* box; - GtkWidget* button; - GtkWidget* label; - GtkWidget* window; /* For optional show interface. */ -} SamplerUI; - -static void -on_load_clicked(GtkWidget* widget, - void* handle) -{ - SamplerUI* ui = (SamplerUI*)handle; - - /* Create a dialog to select a sample file. */ - GtkWidget* dialog = gtk_file_chooser_dialog_new( - "Load Sample", - NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - - /* Run the dialog, and return if it is cancelled. */ - if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { - gtk_widget_destroy(dialog); - return; - } - - /* Get the file path from the dialog. */ - char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - - /* Got what we need, destroy the dialog. */ - gtk_widget_destroy(dialog); - -#define OBJ_BUF_SIZE 1024 - uint8_t obj_buf[OBJ_BUF_SIZE]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE); - - LV2_Atom* msg = write_set_file(&ui->forge, &ui->uris, - filename, strlen(filename)); - - ui->write(ui->controller, 0, lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); - - g_free(filename); -} - -static LV2UI_Handle -instantiate(const LV2UI_Descriptor* descriptor, - const char* plugin_uri, - const char* bundle_path, - LV2UI_Write_Function write_function, - LV2UI_Controller controller, - LV2UI_Widget* widget, - const LV2_Feature* const* features) -{ - SamplerUI* ui = (SamplerUI*)malloc(sizeof(SamplerUI)); - ui->map = NULL; - ui->write = write_function; - ui->controller = controller; - ui->box = NULL; - ui->button = NULL; - ui->label = NULL; - ui->window = NULL; - - *widget = NULL; - - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - ui->map = (LV2_URID_Map*)features[i]->data; - } - } - - if (!ui->map) { - fprintf(stderr, "sampler_ui: Host does not support urid:Map\n"); - free(ui); - return NULL; - } - - map_sampler_uris(ui->map, &ui->uris); - - lv2_atom_forge_init(&ui->forge, ui->map); - - ui->box = gtk_vbox_new(FALSE, 4); - ui->label = gtk_label_new("?"); - ui->button = gtk_button_new_with_label("Load Sample"); - gtk_box_pack_start(GTK_BOX(ui->box), ui->label, TRUE, TRUE, 4); - gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, FALSE, 4); - g_signal_connect(ui->button, "clicked", - G_CALLBACK(on_load_clicked), - ui); - - *widget = ui->box; - - return ui; -} - -static void -cleanup(LV2UI_Handle handle) -{ - SamplerUI* ui = (SamplerUI*)handle; - gtk_widget_destroy(ui->button); - free(ui); -} - -static void -port_event(LV2UI_Handle handle, - uint32_t port_index, - uint32_t buffer_size, - uint32_t format, - const void* buffer) -{ - SamplerUI* ui = (SamplerUI*)handle; - if (format == ui->uris.atom_eventTransfer) { - const LV2_Atom* atom = (const LV2_Atom*)buffer; - if (atom->type == ui->uris.atom_Blank) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; - const LV2_Atom* file_uri = read_set_file(&ui->uris, obj); - if (!file_uri) { - fprintf(stderr, "Unknown message sent to UI.\n"); - return; - } - - const char* uri = (const char*)LV2_ATOM_BODY_CONST(file_uri); - gtk_label_set_text(GTK_LABEL(ui->label), uri); - } else { - fprintf(stderr, "Unknown message type.\n"); - } - } else { - fprintf(stderr, "Unknown format.\n"); - } -} - -/* Optional non-embedded UI show interface. */ -static int -ui_show(LV2UI_Handle handle) -{ - SamplerUI* ui = (SamplerUI*)handle; - - int argc = 0; - gtk_init(&argc, NULL); - - ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_container_add(GTK_CONTAINER(ui->window), ui->box); - gtk_widget_show_all(ui->window); - gtk_window_present(GTK_WINDOW(ui->window)); - - return 0; -} - -/* Optional non-embedded UI hide interface. */ -static int -ui_hide(LV2UI_Handle handle) -{ - return 0; -} - -/* Idle interface for optional non-embedded UI. */ -static int -ui_idle(LV2UI_Handle handle) -{ - SamplerUI* ui = (SamplerUI*)handle; - if (ui->window) { - gtk_main_iteration(); - } - return 0; -} - -static const void* -extension_data(const char* uri) -{ - static const LV2UI_Show_Interface show = { ui_show, ui_hide }; - static const LV2UI_Idle_Interface idle = { ui_idle }; - if (!strcmp(uri, LV2_UI__showInterface)) { - return &show; - } else if (!strcmp(uri, LV2_UI__idleInterface)) { - return &idle; - } - return NULL; -} - -static const LV2UI_Descriptor descriptor = { - SAMPLER_UI_URI, - instantiate, - cleanup, - port_event, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2UI_Descriptor* -lv2ui_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg04-sampler.lv2/uris.h b/plugins/eg04-sampler.lv2/uris.h deleted file mode 100644 index 8e9faee..0000000 --- a/plugins/eg04-sampler.lv2/uris.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2012 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. -*/ - -#ifndef SAMPLER_URIS_H -#define SAMPLER_URIS_H - -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" - -#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" -#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" -#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample" -#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample" - -typedef struct { - LV2_URID atom_Blank; - LV2_URID atom_Path; - LV2_URID atom_Resource; - LV2_URID atom_Sequence; - LV2_URID atom_URID; - LV2_URID atom_eventTransfer; - LV2_URID eg_applySample; - LV2_URID eg_sample; - LV2_URID eg_freeSample; - LV2_URID midi_Event; - LV2_URID patch_Set; - LV2_URID patch_property; - LV2_URID patch_value; -} SamplerURIs; - -static inline void -map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) -{ - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); - uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); - uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); - uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); - uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); - uris->eg_applySample = map->map(map->handle, EG_SAMPLER__applySample); - uris->eg_freeSample = map->map(map->handle, EG_SAMPLER__freeSample); - uris->eg_sample = map->map(map->handle, EG_SAMPLER__sample); - uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); - uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); - uris->patch_property = map->map(map->handle, LV2_PATCH__property); - uris->patch_value = map->map(map->handle, LV2_PATCH__value); -} - -/** - * Write a message like the following to @p forge: - * [] - * a patch:Set ; - * patch:property eg:sample ; - * patch:value . - */ -static inline LV2_Atom* -write_set_file(LV2_Atom_Forge* forge, - const SamplerURIs* uris, - const char* filename, - const uint32_t filename_len) -{ - LV2_Atom_Forge_Frame frame; - LV2_Atom* set = (LV2_Atom*)lv2_atom_forge_object( - forge, &frame, 0, uris->patch_Set); - - lv2_atom_forge_key(forge, uris->patch_property); - lv2_atom_forge_urid(forge, uris->eg_sample); - lv2_atom_forge_key(forge, uris->patch_value); - lv2_atom_forge_path(forge, filename, filename_len); - - lv2_atom_forge_pop(forge, &frame); - - return set; -} - -/** - * Get the file path from a message like: - * [] - * a patch:Set ; - * patch:property eg:sample ; - * patch:value . - */ -static inline const LV2_Atom* -read_set_file(const SamplerURIs* uris, - const LV2_Atom_Object* obj) -{ - if (obj->body.otype != uris->patch_Set) { - fprintf(stderr, "Ignoring unknown message type %d\n", obj->body.otype); - return NULL; - } - - /* Get property URI. */ - const LV2_Atom* property = NULL; - lv2_atom_object_get(obj, uris->patch_property, &property, 0); - if (!property) { - fprintf(stderr, "Malformed set message has no body.\n"); - return NULL; - } else if (property->type != uris->atom_URID) { - fprintf(stderr, "Malformed set message has non-URID property.\n"); - return NULL; - } else if (((const LV2_Atom_URID*)property)->body != uris->eg_sample) { - fprintf(stderr, "Set message for unknown property.\n"); - return NULL; - } - - /* Get value. */ - const LV2_Atom* file_path = NULL; - lv2_atom_object_get(obj, uris->patch_value, &file_path, 0); - if (!file_path) { - fprintf(stderr, "Malformed set message has no value.\n"); - return NULL; - } else if (file_path->type != uris->atom_Path) { - fprintf(stderr, "Set message value is not a Path.\n"); - return NULL; - } - - return file_path; -} - -#endif /* SAMPLER_URIS_H */ diff --git a/plugins/eg04-sampler.lv2/waf b/plugins/eg04-sampler.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg04-sampler.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg04-sampler.lv2/wscript b/plugins/eg04-sampler.lv2/wscript deleted file mode 100644 index 732c904..0000000 --- a/plugins/eg04-sampler.lv2/wscript +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-sampler.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Sampler Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') - - autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', - atleast_version='1.0.0', mandatory=True) - autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', - atleast_version='2.18.0', mandatory=False) - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-sampler.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = '%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Copy other data files to build bundle (build/eg-sampler.lv2) - for i in ['sampler.ttl', 'click.wav']: - bld(features = 'subst', - is_copy = True, - source = i, - target = '%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Use LV2 headers from parent directory if building as a sub-project - includes = ['.'] - if autowaf.is_child: - includes += ['../..'] - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'sampler.c', - name = 'sampler', - target = '%s/sampler' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'SNDFILE LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat - - # Build UI library - if bld.is_defined('HAVE_GTK2'): - obj = bld(features = 'c cshlib', - source = 'sampler_ui.c', - name = 'sampler_ui', - target = '%s/sampler_ui' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'GTK2 LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg05-scope.lv2/README.txt b/plugins/eg05-scope.lv2/README.txt deleted file mode 100644 index 122794c..0000000 --- a/plugins/eg05-scope.lv2/README.txt +++ /dev/null @@ -1,32 +0,0 @@ -== Simple Oscilloscope == - -This plugin displays the waveform of an incoming audio signal using a simple -GTK+Cairo GUI. - -This plugin illustrates: - -- UI <==> Plugin communication via http://lv2plug.in/ns/ext/atom/[LV2 Atom] events -- Atom vector usage and resize-port extension -- Save/Restore UI state by communicating state to backend -- Saving simple key/value state via the http://lv2plug.in/ns/ext/state/[LV2 State] extension -- Cairo drawing and partial exposure - -This plugin intends to outline the basics for building visualization plugins -that rely on atom communication. The UI looks like an oscilloscope, but is not -a real oscilloscope implementation: - -- There is no display synchronisation, results will depend on LV2 host. -- It displays raw audio samples, which a proper scope must not do. -- The display itself just connects min/max line segments. -- No triggering or synchronization. -- No labels, no scale, no calibration, no markers, no numeric readout, etc. - -Addressing these issues is beyond the scope of this example. - -Please see http://lac.linuxaudio.org/2013/papers/36.pdf for scope design, -https://wiki.xiph.org/Videos/Digital_Show_and_Tell for background information, -and http://lists.lv2plug.in/pipermail/devel-lv2plug.in/2013-November/000545.html -for general LV2 related conceptual criticism regarding real-time visualizations. - -A proper oscilloscope based on this example can be found at -https://github.com/x42/sisco.lv2 diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c deleted file mode 100644 index 6d1e953..0000000 --- a/plugins/eg05-scope.lv2/examploscope.c +++ /dev/null @@ -1,423 +0,0 @@ -/* - Copyright 2013 Robin Gareus - - 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 -#include -#include - -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/log/logger.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#include "./uris.h" - -/** - ==== Private Plugin Instance Structure ==== - - In addition to the usual port buffers and features, this plugin stores the - state of the UI here, so it can be opened and closed without losing the - current settings. The UI state is communicated between the plugin and the - UI using atom messages via a sequence port, similarly to MIDI I/O. -*/ -typedef struct { - // Port buffers - float* input[2]; - float* output[2]; - const LV2_Atom_Sequence* control; - LV2_Atom_Sequence* notify; - - // Atom forge and URI mapping - LV2_URID_Map* map; - ScoLV2URIs uris; - LV2_Atom_Forge forge; - LV2_Atom_Forge_Frame frame; - - // Log feature and convenience API - LV2_Log_Log* log; - LV2_Log_Logger logger; - - // Instantiation settings - uint32_t n_channels; - double rate; - - // UI state - bool ui_active; - bool send_settings_to_ui; - float ui_amp; - uint32_t ui_spp; -} EgScope; - -/** ==== Port Indices ==== */ -typedef enum { - SCO_CONTROL = 0, // Event input - SCO_NOTIFY = 1, // Event output - SCO_INPUT0 = 2, // Audio input 0 - SCO_OUTPUT0 = 3, // Audio output 0 - SCO_INPUT1 = 4, // Audio input 1 (stereo variant) - SCO_OUTPUT1 = 5, // Audio input 2 (stereo variant) -} PortIndex; - -/** ==== Instantiate Method ==== */ -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature* const* features) -{ - (void)descriptor; // Unused variable - (void)bundle_path; // Unused variable - - // Allocate and initialise instance structure. - EgScope* self = (EgScope*)calloc(1, sizeof(EgScope)); - if (!self) { - return NULL; - } - - // Get host features - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID__map)) { - self->map = (LV2_URID_Map*)features[i]->data; - } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { - self->log = (LV2_Log_Log*)features[i]->data; - } - } - - if (!self->map) { - fprintf(stderr, "EgScope.lv2 error: Host does not support urid:map\n"); - free(self); - return NULL; - } - - // Decide which variant to use depending on the plugin URI - if (!strcmp(descriptor->URI, SCO_URI "#Stereo")) { - self->n_channels = 2; - } else if (!strcmp(descriptor->URI, SCO_URI "#Mono")) { - self->n_channels = 1; - } else { - free(self); - return NULL; - } - - // Initialise local variables - self->ui_active = false; - self->send_settings_to_ui = false; - self->rate = rate; - - // Set default UI settings - self->ui_spp = 50; - self->ui_amp = 1.0; - - // Map URIs and initialise forge/logger - map_sco_uris(self->map, &self->uris); - lv2_atom_forge_init(&self->forge, self->map); - lv2_log_logger_init(&self->logger, self->map, self->log); - - return (LV2_Handle)self; -} - -/** ==== Connect Port Method ==== */ -static void -connect_port(LV2_Handle handle, - uint32_t port, - void* data) -{ - EgScope* self = (EgScope*)handle; - - switch ((PortIndex)port) { - case SCO_CONTROL: - self->control = (const LV2_Atom_Sequence*)data; - break; - case SCO_NOTIFY: - self->notify = (LV2_Atom_Sequence*)data; - break; - case SCO_INPUT0: - self->input[0] = (float*)data; - break; - case SCO_OUTPUT0: - self->output[0] = (float*)data; - break; - case SCO_INPUT1: - self->input[1] = (float*)data; - break; - case SCO_OUTPUT1: - self->output[1] = (float*)data; - break; - } -} - -/** - ==== Utility Function: `tx_rawaudio` ==== - - This function forges a message for sending a vector of raw data. The object - is a http://lv2plug.in/ns/ext/atom#Blank[Blank] with a few properties, like: - [source,n3] - -------- - [] - a sco:RawAudio ; - sco:channelID 0 ; - sco:audioData [ 0.0, 0.0, ... ] . - -------- - - where the value of the `sco:audioData` property, `[ 0.0, 0.0, ... ]`, is a - http://lv2plug.in/ns/ext/atom#Vector[Vector] of - http://lv2plug.in/ns/ext/atom#Float[Float]. -*/ -static void -tx_rawaudio(LV2_Atom_Forge* forge, - ScoLV2URIs* uris, - const int32_t channel, - const size_t n_samples, - const float* data) -{ - LV2_Atom_Forge_Frame frame; - - // Forge container object of type 'RawAudio' - lv2_atom_forge_frame_time(forge, 0); - lv2_atom_forge_object(forge, &frame, 0, uris->RawAudio); - - // Add integer 'channelID' property - lv2_atom_forge_key(forge, uris->channelID); - lv2_atom_forge_int(forge, channel); - - // Add vector of floats 'audioData' property - lv2_atom_forge_key(forge, uris->audioData); - lv2_atom_forge_vector( - forge, sizeof(float), uris->atom_Float, n_samples, data); - - // Close off object - lv2_atom_forge_pop(forge, &frame); -} - -/** ==== Run Method ==== */ -static void -run(LV2_Handle handle, uint32_t n_samples) -{ - EgScope* self = (EgScope*)handle; - - /* Ensure notify port buffer is large enough to hold all audio-samples and - configuration settings. A minimum size was requested in the .ttl file, - but check here just to be sure. - - TODO: Explain these magic numbers. - */ - const size_t size = (sizeof(float) * n_samples + 64) * self->n_channels; - const uint32_t space = self->notify->atom.size; - if (space < size + 128) { - /* Insufficient space, report error and do nothing. Note that a - real-time production plugin mustn't call log functions in run(), but - this can be useful for debugging and example purposes. - */ - lv2_log_error(&self->logger, "Buffer size is insufficient\n"); - return; - } - - // Prepare forge buffer and initialize atom-sequence - lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->notify, space); - lv2_atom_forge_sequence_head(&self->forge, &self->frame, 0); - - /* Send settings to UI - - The plugin can continue to run while the UI is closed and re-opened. - The state and settings of the UI are kept here and transmitted to the UI - every time it asks for them or if the user initializes a 'load preset'. - */ - if (self->send_settings_to_ui && self->ui_active) { - self->send_settings_to_ui = false; - // Forge container object of type 'ui_state' - LV2_Atom_Forge_Frame frame; - lv2_atom_forge_frame_time(&self->forge, 0); - lv2_atom_forge_object(&self->forge, &frame, 0, self->uris.ui_State); - - // Add UI state as properties - lv2_atom_forge_key(&self->forge, self->uris.ui_spp); - lv2_atom_forge_int(&self->forge, self->ui_spp); - lv2_atom_forge_key(&self->forge, self->uris.ui_amp); - lv2_atom_forge_float(&self->forge, self->ui_amp); - lv2_atom_forge_key(&self->forge, self->uris.param_sampleRate); - lv2_atom_forge_float(&self->forge, self->rate); - lv2_atom_forge_pop(&self->forge, &frame); - } - - // Process incoming events from GUI - if (self->control) { - const LV2_Atom_Event* ev = lv2_atom_sequence_begin( - &(self->control)->body); - // For each incoming message... - while (!lv2_atom_sequence_is_end( - &self->control->body, self->control->atom.size, ev)) { - // If the event is an atom:Blank object - if (ev->body.type == self->uris.atom_Blank) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; - if (obj->body.otype == self->uris.ui_On) { - // If the object is a ui-on, the UI was activated - self->ui_active = true; - self->send_settings_to_ui = true; - } else if (obj->body.otype == self->uris.ui_Off) { - // If the object is a ui-off, the UI was closed - self->ui_active = false; - } else if (obj->body.otype == self->uris.ui_State) { - // If the object is a ui-state, it's the current UI settings - const LV2_Atom* spp = NULL; - const LV2_Atom* amp = NULL; - lv2_atom_object_get(obj, self->uris.ui_spp, &spp, - self->uris.ui_amp, &, - 0); - if (spp) { - self->ui_spp = ((const LV2_Atom_Int*)spp)->body; - } - if (amp) { - self->ui_amp = ((const LV2_Atom_Float*)amp)->body; - } - } - } - ev = lv2_atom_sequence_next(ev); - } - } - - // Process audio data - for (uint32_t c = 0; c < self->n_channels; ++c) { - if (self->ui_active) { - // If UI is active, send raw audio data to UI - tx_rawaudio(&self->forge, &self->uris, c, n_samples, self->input[c]); - } - // If not processing audio in-place, forward audio - if (self->input[c] != self->output[c]) { - memcpy(self->output[c], self->input[c], sizeof(float) * n_samples); - } - } - - // Close off sequence - lv2_atom_forge_pop(&self->forge, &self->frame); -} - -static void -cleanup(LV2_Handle handle) -{ - free(handle); -} - - -/** - ==== State Methods ==== - - This plugin's state consists of two basic properties: one `int` and one - `float`. No files are used. Note these values are POD, but not portable, - since different machines may have a different integer endianness or floating - point format. However, since standard Atom types are used, a good host will - be able to save them portably as text anyway. -*/ -static LV2_State_Status -state_save(LV2_Handle instance, - LV2_State_Store_Function store, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -{ - EgScope* self = (EgScope*)instance; - if (!self) { - return LV2_STATE_SUCCESS; - } - - store(handle, self->uris.ui_spp, - (void*)&self->ui_spp, sizeof(uint32_t), - self->uris.atom_Int, - LV2_STATE_IS_POD); - - store(handle, self->uris.ui_amp, - (void*)&self->ui_amp, sizeof(float), - self->uris.atom_Float, - LV2_STATE_IS_POD); - - return LV2_STATE_SUCCESS; -} - -static LV2_State_Status -state_restore(LV2_Handle instance, - LV2_State_Retrieve_Function retrieve, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -{ - EgScope* self = (EgScope*)instance; - - size_t size; - uint32_t type; - uint32_t valflags; - - const void* spp = retrieve( - handle, self->uris.ui_spp, &size, &type, &valflags); - if (spp && size == sizeof(uint32_t) && type == self->uris.atom_Int) { - self->ui_spp = *((const uint32_t*)spp); - self->send_settings_to_ui = true; - } - - const void* amp = retrieve( - handle, self->uris.ui_amp, &size, &type, &valflags); - if (amp && size == sizeof(float) && type == self->uris.atom_Float) { - self->ui_amp = *((const float*)amp); - self->send_settings_to_ui = true; - } - - return LV2_STATE_SUCCESS; -} - -static const void* -extension_data(const char* uri) -{ - static const LV2_State_Interface state = { state_save, state_restore }; - if (!strcmp(uri, LV2_STATE__interface)) { - return &state; - } - return NULL; -} - -/** ==== Plugin Descriptors ==== */ -static const LV2_Descriptor descriptor_mono = { - SCO_URI "#Mono", - instantiate, - connect_port, - NULL, - run, - NULL, - cleanup, - extension_data -}; - -static const LV2_Descriptor descriptor_stereo = { - SCO_URI "#Stereo", - instantiate, - connect_port, - NULL, - run, - NULL, - cleanup, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor_mono; - case 1: - return &descriptor_stereo; - default: - return NULL; - } -} diff --git a/plugins/eg05-scope.lv2/examploscope.ttl.in b/plugins/eg05-scope.lv2/examploscope.ttl.in deleted file mode 100644 index 0b76962..0000000 --- a/plugins/eg05-scope.lv2/examploscope.ttl.in +++ /dev/null @@ -1,130 +0,0 @@ -@prefix atom: . -@prefix bufsz: . -@prefix doap: . -@prefix foaf: . -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . -@prefix urid: . -@prefix rsz: . -@prefix state: . -@prefix egscope: . - - - a foaf:Person ; - foaf:name "Robin Gareus" ; - foaf:mbox ; - foaf:homepage . - - - a doap:Project ; - doap:maintainer ; - doap:name "Example Scope" . - -egscope:Mono - a lv2:Plugin, lv2:AnalyserPlugin ; - doap:name "Example Scope (Mono)" ; - lv2:project ; - doap:license ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:extensionData state:interface ; - ui:ui egscope:ui ; - lv2:port [ - a atom:AtomPort , - lv2:InputPort ; - atom:bufferType atom:Sequence ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a atom:AtomPort , - lv2:OutputPort ; - atom:bufferType atom:Sequence ; - lv2:designation lv2:control ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify" ; - # 8192 * sizeof(float) + LV2-Atoms - rsz:minimumSize 32832; - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 2 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 3 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . - - -egscope:Stereo - a lv2:Plugin, lv2:AnalyserPlugin ; - doap:name "Example Scope (Stereo)" ; - lv2:project ; - doap:license ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:extensionData state:interface ; - ui:ui egscope:ui ; - lv2:port [ - a atom:AtomPort , - lv2:InputPort ; - atom:bufferType atom:Sequence ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a atom:AtomPort , - lv2:OutputPort ; - atom:bufferType atom:Sequence ; - lv2:designation lv2:control ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify" ; - rsz:minimumSize 65664; - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 2 ; - lv2:symbol "in0" ; - lv2:name "InL" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 3 ; - lv2:symbol "out0" ; - lv2:name "OutL" - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 4 ; - lv2:symbol "in1" ; - lv2:name "InR" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 5 ; - lv2:symbol "out1" ; - lv2:name "OutR" - ] . - - -egscope:ui - a ui:GtkUI ; - lv2:requiredFeature urid:map ; - ui:portNotification [ - ui:plugin egscope:Mono ; - lv2:symbol "notify" ; - ui:notifyType atom:Blank - ] , [ - ui:plugin egscope:Stereo ; - lv2:symbol "notify" ; - ui:notifyType atom:Blank - ] . diff --git a/plugins/eg05-scope.lv2/examploscope_ui.c b/plugins/eg05-scope.lv2/examploscope_ui.c deleted file mode 100644 index a8c1e2c..0000000 --- a/plugins/eg05-scope.lv2/examploscope_ui.c +++ /dev/null @@ -1,655 +0,0 @@ -/* - Copyright 2013 Robin Gareus - - 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 -#include - -#include -#include - -#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" -#include "./uris.h" - -// Drawing area size -#define DAWIDTH (640) -#define DAHEIGHT (200) - -/** - Max continuous points on path. Many short-path segments are - expensive|inefficient long paths are not supported by all surfaces (usually - its a miter - not point - limit, depending on used cairo backend) -*/ -#define MAX_CAIRO_PATH (128) - -/** - Representation of the raw audio-data for display (min | max) values for a - given 'index' position. -*/ -typedef struct { - float data_min[DAWIDTH]; - float data_max[DAWIDTH]; - - uint32_t idx; - uint32_t sub; -} ScoChan; - -typedef struct { - LV2_Atom_Forge forge; - LV2_URID_Map* map; - ScoLV2URIs uris; - - LV2UI_Write_Function write; - LV2UI_Controller controller; - - GtkWidget* hbox; - GtkWidget* vbox; - GtkWidget* sep[2]; - GtkWidget* darea; - GtkWidget* btn_pause; - GtkWidget* lbl_speed; - GtkWidget* lbl_amp; - GtkWidget* spb_speed; - GtkWidget* spb_amp; - GtkAdjustment* spb_speed_adj; - GtkAdjustment* spb_amp_adj; - - ScoChan chn[2]; - uint32_t stride; - uint32_t n_channels; - bool paused; - float rate; -} EgScopeUI; - - -/** Send current UI settings to backend. */ -static void -send_ui_state(LV2UI_Handle handle) -{ - EgScopeUI* ui = (EgScopeUI*)handle; - const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); - - // Use local buffer on the stack to build atom - uint8_t obj_buf[1024]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - - // Start a ui:State object - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_State); - - // msg[samples-per-pixel] = integer - lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp); - lv2_atom_forge_int(&ui->forge, ui->stride); - - // msg[amplitude] = float - lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp); - lv2_atom_forge_float(&ui->forge, gain); - - // Finish ui:State object - lv2_atom_forge_pop(&ui->forge, &frame); - - // Send message to plugin port '0' - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); -} - -/** Notify backend that UI is closed. */ -static void -send_ui_disable(LV2UI_Handle handle) -{ - EgScopeUI* ui = (EgScopeUI*)handle; - send_ui_state(handle); - - uint8_t obj_buf[64]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_Off); - lv2_atom_forge_pop(&ui->forge, &frame); - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); -} - -/** - Notify backend that UI is active. - - The plugin should send state and enable data transmission. -*/ -static void -send_ui_enable(LV2UI_Handle handle) -{ - EgScopeUI* ui = (EgScopeUI*)handle; - - uint8_t obj_buf[64]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_On); - lv2_atom_forge_pop(&ui->forge, &frame); - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); -} - -/** Gtk widget callback. */ -static gboolean -on_cfg_changed(GtkWidget* widget, gpointer data) -{ - send_ui_state(data); - return TRUE; -} - -/** - Gdk drawing area draw callback. - - Called in Gtk's main thread and uses Cairo to draw the data. -*/ -static gboolean -on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data) -{ - EgScopeUI* ui = (EgScopeUI*)data; - const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); - - // Get cairo type for the gtk window - cairo_t* cr; - cr = gdk_cairo_create(ui->darea->window); - - // Limit cairo-drawing to exposed area - cairo_rectangle(cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height); - cairo_clip(cr); - - // Clear background - cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); - cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels); - cairo_fill(cr); - - cairo_set_line_width(cr, 1.0); - - const uint32_t start = ev->area.x; - const uint32_t end = ev->area.x + ev->area.width; - - assert(start < DAWIDTH); - assert(end <= DAWIDTH); - assert(start < end); - - for (uint32_t c = 0; c < ui->n_channels; ++c) { - ScoChan* chn = &ui->chn[c]; - - /* Drawing area Y-position of given sample-value. - * Note: cairo-pixel at 0 spans -0.5 .. +0.5, hence (DAHEIGHT / 2.0 -0.5) - * also the cairo Y-axis points upwards (hence 'minus value') - * - * == ( DAHEIGHT * (CHN) // channel offset - * + (DAHEIGHT / 2) - 0.5 // vertical center -- '0' - * - (DAHEIGHT / 2) * (VAL) * (GAIN) - * ) - */ - const float chn_y_offset = DAHEIGHT * c + DAHEIGHT * 0.5f - 0.5f; - const float chn_y_scale = DAHEIGHT * 0.5f * gain; - -#define CYPOS(VAL) (chn_y_offset - (VAL) * chn_y_scale) - - cairo_save(cr); - - /* Restrict drawing to current channel area, don't bleed drawing into - neighboring channels. */ - cairo_rectangle(cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT); - cairo_clip(cr); - - // Set color of wave-form - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); - - /* This is a somewhat 'smart' mechanism to plot audio data using - alternating up/down line-directions. It works well for both cases: - 1 pixel <= 1 sample and 1 pixel represents more than 1 sample, but - is not ideal for either. */ - if (start == chn->idx) { - cairo_move_to(cr, start - 0.5, CYPOS(0)); - } else { - cairo_move_to(cr, start - 0.5, CYPOS(chn->data_max[start])); - } - - uint32_t pathlength = 0; - for (uint32_t i = start; i < end; ++i) { - if (i == chn->idx) { - continue; - } else if (i % 2) { - cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); - cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); - ++pathlength; - } else { - cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); - cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); - ++pathlength; - } - - /** Limit the max cairo path length. This is an optimization trade - off: too short path: high load CPU/GPU load. too-long path: - bad anti-aliasing, or possibly lost points */ - if (pathlength > MAX_CAIRO_PATH) { - pathlength = 0; - cairo_stroke(cr); - if (i % 2) { - cairo_move_to(cr, i - .5, CYPOS(chn->data_max[i])); - } else { - cairo_move_to(cr, i - .5, CYPOS(chn->data_min[i])); - } - } - } - - if (pathlength > 0) { - cairo_stroke(cr); - } - - // Draw current position vertical line if display is slow - if (ui->stride >= ui->rate / 4800.0f || ui->paused) { - cairo_set_source_rgba(cr, .9, .2, .2, .6); - cairo_move_to(cr, chn->idx - .5, DAHEIGHT * c); - cairo_line_to(cr, chn->idx - .5, DAHEIGHT * (c + 1)); - cairo_stroke(cr); - } - - // Undo the 'clipping' restriction - cairo_restore(cr); - - // Channel separator - if (c > 0) { - cairo_set_source_rgba(cr, .5, .5, .5, 1.0); - cairo_move_to(cr, 0, DAHEIGHT * c - .5); - cairo_line_to(cr, DAWIDTH, DAHEIGHT * c - .5); - cairo_stroke(cr); - } - - // Zero scale line - cairo_set_source_rgba(cr, .3, .3, .7, .5); - cairo_move_to(cr, 0, DAHEIGHT * (c + .5) - .5); - cairo_line_to(cr, DAWIDTH, DAHEIGHT * (c + .5) - .5); - cairo_stroke(cr); - } - - cairo_destroy(cr); - return TRUE; -} - -/** - Parse raw audio data and prepare for later drawing. - - Note this is a toy example, which is really a waveform display, not an - oscilloscope. A serious scope would not display samples as is. - - Signals above ~ 1/10 of the sampling-rate will not yield a useful visual - display and result in a rather unintuitive representation of the actual - waveform. - - Ideally the audio-data would be buffered and upsampled here and after that - written in a display buffer for later use. - - For more information, see - https://wiki.xiph.org/Videos/Digital_Show_and_Tell - http://lac.linuxaudio.org/2013/papers/36.pdf - and https://github.com/x42/sisco.lv2 -*/ -static int -process_channel(EgScopeUI* ui, - ScoChan* chn, - const size_t n_elem, - float const* data, - uint32_t* idx_start, - uint32_t* idx_end) -{ - int overflow = 0; - *idx_start = chn->idx; - for (size_t i = 0; i < n_elem; ++i) { - if (data[i] < chn->data_min[chn->idx]) { - chn->data_min[chn->idx] = data[i]; - } - if (data[i] > chn->data_max[chn->idx]) { - chn->data_max[chn->idx] = data[i]; - } - if (++chn->sub >= ui->stride) { - chn->sub = 0; - chn->idx = (chn->idx + 1) % DAWIDTH; - if (chn->idx == 0) { - ++overflow; - } - chn->data_min[chn->idx] = 1.0; - chn->data_max[chn->idx] = -1.0; - } - } - *idx_end = chn->idx; - return overflow; -} - -/** - Called via port_event() which is called by the host, typically at a rate of - around 25 FPS. -*/ -static void -update_scope(EgScopeUI* ui, - const int32_t channel, - const size_t n_elem, - float const* data) -{ - // Never trust input data which could lead to application failure. - if (channel < 0 || (uint32_t)channel > ui->n_channels) { - return; - } - - // Update state in sync with 1st channel - if (channel == 0) { - ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed)); - const bool paused = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(ui->btn_pause)); - - if (paused != ui->paused) { - ui->paused = paused; - gtk_widget_queue_draw(ui->darea); - } - } - if (ui->paused) { - return; - } - - uint32_t idx_start; // Display pixel start - uint32_t idx_end; // Display pixel end - int overflow; // Received more audio-data than display-pixel - - // Process this channel's audio-data for display - ScoChan* chn = &ui->chn[channel]; - overflow = process_channel(ui, chn, n_elem, data, &idx_start, &idx_end); - - // Signal gtk's main thread to redraw the widget after the last channel - if ((uint32_t)channel + 1 == ui->n_channels) { - if (overflow > 1) { - // Redraw complete widget - gtk_widget_queue_draw(ui->darea); - } else if (idx_end > idx_start) { - // Redraw area between start -> end pixel - gtk_widget_queue_draw_area(ui->darea, idx_start - 2, 0, 3 - + idx_end - idx_start, - DAHEIGHT * ui->n_channels); - } else if (idx_end < idx_start) { - // Wrap-around: redraw area between 0->start AND end->right-end - gtk_widget_queue_draw_area( - ui->darea, - idx_start - 2, 0, - 3 + DAWIDTH - idx_start, DAHEIGHT * ui->n_channels); - gtk_widget_queue_draw_area( - ui->darea, - 0, 0, - idx_end + 1, DAHEIGHT * ui->n_channels); - } - } -} - -static LV2UI_Handle -instantiate(const LV2UI_Descriptor* descriptor, - const char* plugin_uri, - const char* bundle_path, - LV2UI_Write_Function write_function, - LV2UI_Controller controller, - LV2UI_Widget* widget, - const LV2_Feature* const* features) -{ - EgScopeUI* ui = (EgScopeUI*)malloc(sizeof(EgScopeUI)); - - if (!ui) { - fprintf(stderr, "EgScope.lv2 UI: out of memory\n"); - return NULL; - } - - ui->map = NULL; - *widget = NULL; - - if (!strcmp(plugin_uri, SCO_URI "#Mono")) { - ui->n_channels = 1; - } else if (!strcmp(plugin_uri, SCO_URI "#Stereo")) { - ui->n_channels = 2; - } else { - free(ui); - return NULL; - } - - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - ui->map = (LV2_URID_Map*)features[i]->data; - } - } - - if (!ui->map) { - fprintf(stderr, "EgScope.lv2 UI: Host does not support urid:map\n"); - free(ui); - return NULL; - } - - // Initialize private data structure - ui->write = write_function; - ui->controller = controller; - - ui->vbox = NULL; - ui->hbox = NULL; - ui->darea = NULL; - ui->stride = 25; - ui->paused = false; - ui->rate = 48000; - - ui->chn[0].idx = 0; - ui->chn[0].sub = 0; - ui->chn[1].idx = 0; - ui->chn[1].sub = 0; - memset(ui->chn[0].data_min, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[0].data_max, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[1].data_min, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[1].data_max, 0, sizeof(float) * DAWIDTH); - - map_sco_uris(ui->map, &ui->uris); - lv2_atom_forge_init(&ui->forge, ui->map); - - // Setup UI - ui->hbox = gtk_hbox_new(FALSE, 0); - ui->vbox = gtk_vbox_new(FALSE, 0); - - ui->darea = gtk_drawing_area_new(); - gtk_widget_set_size_request(ui->darea, DAWIDTH, DAHEIGHT * ui->n_channels); - - ui->lbl_speed = gtk_label_new("Samples/Pixel"); - ui->lbl_amp = gtk_label_new("Amplitude"); - - ui->sep[0] = gtk_hseparator_new(); - ui->sep[1] = gtk_label_new(""); - ui->btn_pause = gtk_toggle_button_new_with_label("Pause"); - - ui->spb_speed_adj = (GtkAdjustment*)gtk_adjustment_new( - 25.0, 1.0, 1000.0, 1.0, 5.0, 0.0); - ui->spb_speed = gtk_spin_button_new(ui->spb_speed_adj, 1.0, 0); - - ui->spb_amp_adj = (GtkAdjustment*)gtk_adjustment_new( - 1.0, 0.1, 6.0, 0.1, 1.0, 0.0); - ui->spb_amp = gtk_spin_button_new(ui->spb_amp_adj, 0.1, 1); - - gtk_box_pack_start(GTK_BOX(ui->hbox), ui->darea, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(ui->hbox), ui->vbox, FALSE, FALSE, 4); - - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_speed, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_speed, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[0], FALSE, FALSE, 8); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_amp, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_amp, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[1], TRUE, FALSE, 8); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->btn_pause, FALSE, FALSE, 2); - - g_signal_connect(G_OBJECT(ui->darea), "expose_event", - G_CALLBACK(on_expose_event), ui); - g_signal_connect(G_OBJECT(ui->spb_amp), "value-changed", - G_CALLBACK(on_cfg_changed), ui); - g_signal_connect(G_OBJECT(ui->spb_speed), "value-changed", - G_CALLBACK(on_cfg_changed), ui); - - *widget = ui->hbox; - - /* Send UIOn message to plugin, which will request state and enable message - transmission. */ - send_ui_enable(ui); - - return ui; -} - -static void -cleanup(LV2UI_Handle handle) -{ - EgScopeUI* ui = (EgScopeUI*)handle; - /* Send UIOff message to plugin, which will save state and disable message - * transmission. */ - send_ui_disable(ui); - gtk_widget_destroy(ui->darea); - free(ui); -} - -static int -recv_raw_audio(EgScopeUI* ui, const LV2_Atom_Object* obj) -{ - const LV2_Atom* chan_val = NULL; - const LV2_Atom* data_val = NULL; - const int n_props = lv2_atom_object_get( - obj, - ui->uris.channelID, &chan_val, - ui->uris.audioData, &data_val, - NULL); - - if (n_props != 2 || - chan_val->type != ui->uris.atom_Int || - data_val->type != ui->uris.atom_Vector) { - // Object does not have the required properties with correct types - fprintf(stderr, "eg-scope.lv2 UI error: Corrupt audio message\n"); - return 1; - } - - // Get the values we need from the body of the property value atoms - const int32_t chn = ((const LV2_Atom_Int*)chan_val)->body; - const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*)data_val; - if (vec->body.child_type != ui->uris.atom_Float) { - return 1; // Vector has incorrect element type - } - - // Number of elements = (total size - header size) / element size - const size_t n_elem = ((data_val->size - sizeof(LV2_Atom_Vector_Body)) - / sizeof(float)); - - // Float elements immediately follow the vector body header - const float* data = (const float*)(&vec->body + 1); - - // Update display - update_scope(ui, chn, n_elem, data); - return 0; -} - -static int -recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj) -{ - const LV2_Atom* spp_val = NULL; - const LV2_Atom* amp_val = NULL; - const LV2_Atom* rate_val = NULL; - const int n_props = lv2_atom_object_get( - obj, - ui->uris.ui_spp, &spp_val, - ui->uris.ui_amp, &_val, - ui->uris.param_sampleRate, &rate_val, - NULL); - - if (n_props != 3 || - spp_val->type != ui->uris.atom_Int || - amp_val->type != ui->uris.atom_Float || - rate_val->type != ui->uris.atom_Float) { - // Object does not have the required properties with correct types - fprintf(stderr, "eg-scope.lv2 UI error: Corrupt state message\n"); - return 1; - } - - // Get the values we need from the body of the property value atoms - const int32_t spp = ((const LV2_Atom_Int*)spp_val)->body; - const float amp = ((const LV2_Atom_Float*)amp_val)->body; - const float rate = ((const LV2_Atom_Float*)rate_val)->body; - - // Update UI - gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp), amp); - ui->rate = rate; - - return 0; -} - -/** - Receive data from the DSP-backend. - - This is called by the host, typically at a rate of around 25 FPS. - - Ideally this happens regularly and with relatively low latency, but there - are no hard guarantees about message delivery. -*/ -static void -port_event(LV2UI_Handle handle, - uint32_t port_index, - uint32_t buffer_size, - uint32_t format, - const void* buffer) -{ - EgScopeUI* ui = (EgScopeUI*)handle; - const LV2_Atom* atom = (const LV2_Atom*)buffer; - - /* Check type of data received - * - format == 0: Control port event (float) - * - format > 0: Message (atom) - */ - if (format == ui->uris.atom_eventTransfer && - atom->type == ui->uris.atom_Blank) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; - if (obj->body.otype == ui->uris.RawAudio) { - recv_raw_audio(ui, obj); - } else if (obj->body.otype == ui->uris.ui_State) { - recv_ui_state(ui, obj); - } - } -} - -static const LV2UI_Descriptor descriptor = { - SCO_URI "#ui", - instantiate, - cleanup, - port_event, - NULL -}; - -LV2_SYMBOL_EXPORT -const LV2UI_Descriptor* -lv2ui_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg05-scope.lv2/manifest.ttl.in b/plugins/eg05-scope.lv2/manifest.ttl.in deleted file mode 100644 index a64aff1..0000000 --- a/plugins/eg05-scope.lv2/manifest.ttl.in +++ /dev/null @@ -1,21 +0,0 @@ -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - -# ==== Mono plugin variant ==== - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - -# ==== Stereo plugin variant ==== - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - -# ==== Gtk 2.0 UI ==== - - a ui:GtkUI ; - ui:binary ; - rdfs:seeAlso . diff --git a/plugins/eg05-scope.lv2/uris.h b/plugins/eg05-scope.lv2/uris.h deleted file mode 100644 index bd57551..0000000 --- a/plugins/eg05-scope.lv2/uris.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright 2013 Robin Gareus - - 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. -*/ - -#ifndef SCO_URIS_H -#define SCO_URIS_H - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" -#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" - -#define SCO_URI "http://lv2plug.in/plugins/eg-scope" - -typedef struct { - // URIs defined in LV2 specifications - LV2_URID atom_Blank; - LV2_URID atom_Vector; - LV2_URID atom_Float; - LV2_URID atom_Int; - LV2_URID atom_eventTransfer; - LV2_URID param_sampleRate; - - /* URIs defined for this plugin. It is best to re-use existing URIs as - much as possible, but plugins may need more vocabulary specific to their - needs. These are used as types and properties for plugin:UI - communication, as well as for saving state. */ - LV2_URID RawAudio; - LV2_URID channelID; - LV2_URID audioData; - LV2_URID ui_On; - LV2_URID ui_Off; - LV2_URID ui_State; - LV2_URID ui_spp; - LV2_URID ui_amp; -} ScoLV2URIs; - -static inline void -map_sco_uris(LV2_URID_Map* map, ScoLV2URIs* uris) -{ - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Vector = map->map(map->handle, LV2_ATOM__Vector); - uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); - uris->atom_Int = map->map(map->handle, LV2_ATOM__Int); - uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); - uris->param_sampleRate = map->map(map->handle, LV2_PARAMETERS__sampleRate); - - /* Note the convention that URIs for types are capitalized, and URIs for - everything else (mainly properties) are not, just as in LV2 - specifications. */ - uris->RawAudio = map->map(map->handle, SCO_URI "#RawAudio"); - uris->audioData = map->map(map->handle, SCO_URI "#audioData"); - uris->channelID = map->map(map->handle, SCO_URI "#channelID"); - uris->ui_On = map->map(map->handle, SCO_URI "#UIOn"); - uris->ui_Off = map->map(map->handle, SCO_URI "#UIOff"); - uris->ui_State = map->map(map->handle, SCO_URI "#UIState"); - uris->ui_spp = map->map(map->handle, SCO_URI "#ui-spp"); - uris->ui_amp = map->map(map->handle, SCO_URI "#ui-amp"); -} - -#endif /* SCO_URIS_H */ diff --git a/plugins/eg05-scope.lv2/wscript b/plugins/eg05-scope.lv2/wscript deleted file mode 100644 index 807d15d..0000000 --- a/plugins/eg05-scope.lv2/wscript +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-scope.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Scope Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') - - autowaf.check_pkg(conf, 'cairo', uselib_store='CAIRO', - atleast_version='1.8.10', mandatory=True) - autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', - atleast_version='2.18.0', mandatory=False) - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-scope.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - for i in ['manifest.ttl', 'examploscope.ttl']: - bld(features = 'subst', - source = i + '.in', - target = '%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Use LV2 headers from parent directory if building as a sub-project - includes = ['.'] - if autowaf.is_child: - includes += ['../..'] - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'examploscope.c', - name = 'examploscope', - target = '%s/examploscope' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat - - # Build UI library - if bld.is_defined('HAVE_GTK2'): - obj = bld(features = 'c cshlib', - source = 'examploscope_ui.c', - name = 'examploscope_ui', - target = '%s/examploscope_ui' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'GTK2 CAIRO LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg06-fifths.lv2/README.txt b/plugins/eg06-fifths.lv2/README.txt deleted file mode 100644 index 2154321..0000000 --- a/plugins/eg06-fifths.lv2/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -== Fifths == - -This plugin demonstrates simple MIDI event reading and writing. diff --git a/plugins/eg06-fifths.lv2/fifths.c b/plugins/eg06-fifths.lv2/fifths.c deleted file mode 100644 index c7d12e1..0000000 --- a/plugins/eg06-fifths.lv2/fifths.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - LV2 Fifths Example Plugin - Copyright 2014 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. -*/ - -#include -#include -#include -#ifndef __cplusplus -# include -#endif - -#include - -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#include "./uris.h" - -enum { - FIFTHS_IN = 0, - FIFTHS_OUT = 1 -}; - -typedef struct { - // Features - LV2_URID_Map* map; - - // Ports - const LV2_Atom_Sequence* in_port; - LV2_Atom_Sequence* out_port; - - // URIs - FifthsURIs uris; -} Fifths; - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Fifths* self = (Fifths*)instance; - switch (port) { - case FIFTHS_IN: - self->in_port = (const LV2_Atom_Sequence*)data; - break; - case FIFTHS_OUT: - self->out_port = (LV2_Atom_Sequence*)data; - break; - default: - break; - } -} - -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* path, - const LV2_Feature* const* features) -{ - // Allocate and initialise instance structure. - Fifths* self = (Fifths*)malloc(sizeof(Fifths)); - if (!self) { - return NULL; - } - memset(self, 0, sizeof(Fifths)); - - // Get host features - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID__map)) { - self->map = (LV2_URID_Map*)features[i]->data; - } - } - if (!self->map) { - fprintf(stderr, "Missing feature urid:map\n"); - free(self); - return NULL; - } - - // Map URIs and initialise forge/logger - map_fifths_uris(self->map, &self->uris); - - return (LV2_Handle)self; -} - -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -static void -run(LV2_Handle instance, - uint32_t sample_count) -{ - Fifths* self = (Fifths*)instance; - FifthsURIs* uris = &self->uris; - - // Struct for a 3 byte MIDI event, used for writing notes - typedef struct { - LV2_Atom_Event event; - uint8_t msg[3]; - } MIDINoteEvent; - - // Initially self->out_port contains a Chunk with size set to capacity - - // Get the capacity - const uint32_t out_capacity = self->out_port->atom.size; - - // Write an empty Sequence header to the output - lv2_atom_sequence_clear(self->out_port); - self->out_port->atom.type = self->in_port->atom.type; - - // Read incoming events - LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) { - if (ev->body.type == uris->midi_Event) { - const uint8_t* const msg = (const uint8_t*)(ev + 1); - switch (lv2_midi_message_type(msg)) { - case LV2_MIDI_MSG_NOTE_ON: - case LV2_MIDI_MSG_NOTE_OFF: - // Forward note to output - lv2_atom_sequence_append_event( - self->out_port, out_capacity, ev); - - const uint8_t note = msg[1]; - if (note <= 127 - 7) { - // Make a note one 5th (7 semitones) higher than input - MIDINoteEvent fifth; - - // Could simply do fifth.event = *ev here instead... - fifth.event.time.frames = ev->time.frames; // Same time - fifth.event.body.type = ev->body.type; // Same type - fifth.event.body.size = ev->body.size; // Same size - - fifth.msg[0] = msg[0]; // Same status - fifth.msg[1] = msg[1] + 7; // Pitch up 7 semitones - fifth.msg[2] = msg[2]; // Same velocity - - // Write 5th event - lv2_atom_sequence_append_event( - self->out_port, out_capacity, &fifth.event); - } - break; - default: - // Forward all other MIDI events directly - lv2_atom_sequence_append_event( - self->out_port, out_capacity, ev); - break; - } - } - } -} - -static const void* -extension_data(const char* uri) -{ - return NULL; -} - -static const LV2_Descriptor descriptor = { - EG_FIFTHS_URI, - instantiate, - connect_port, - NULL, // activate, - run, - NULL, // deactivate, - cleanup, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2_Descriptor* lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg06-fifths.lv2/fifths.ttl b/plugins/eg06-fifths.lv2/fifths.ttl deleted file mode 100644 index 7f58a33..0000000 --- a/plugins/eg06-fifths.lv2/fifths.ttl +++ /dev/null @@ -1,30 +0,0 @@ -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix urid: . -@prefix midi: . - - - a lv2:Plugin ; - doap:name "Example Fifths" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports midi:MidiEvent ; - lv2:index 0 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:OutputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports midi:MidiEvent ; - lv2:index 1 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg06-fifths.lv2/manifest.ttl.in b/plugins/eg06-fifths.lv2/manifest.ttl.in deleted file mode 100644 index f87f2c1..0000000 --- a/plugins/eg06-fifths.lv2/manifest.ttl.in +++ /dev/null @@ -1,8 +0,0 @@ -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg06-fifths.lv2/uris.h b/plugins/eg06-fifths.lv2/uris.h deleted file mode 100644 index e174fb4..0000000 --- a/plugins/eg06-fifths.lv2/uris.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - LV2 Fifths Example Plugin - Copyright 2014 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. -*/ - -#ifndef FIFTHS_URIS_H -#define FIFTHS_URIS_H - -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" - -#define EG_FIFTHS_URI "http://lv2plug.in/plugins/eg-fifths" -#define EG_FIFTHS__sample EG_FIFTHS_URI "#sample" -#define EG_FIFTHS__applySample EG_FIFTHS_URI "#applySample" -#define EG_FIFTHS__freeSample EG_FIFTHS_URI "#freeSample" - -typedef struct { - LV2_URID atom_Blank; - LV2_URID atom_Path; - LV2_URID atom_Resource; - LV2_URID atom_Sequence; - LV2_URID atom_URID; - LV2_URID atom_eventTransfer; - LV2_URID eg_applySample; - LV2_URID eg_sample; - LV2_URID eg_freeSample; - LV2_URID midi_Event; - LV2_URID patch_Set; - LV2_URID patch_property; - LV2_URID patch_value; -} FifthsURIs; - -static inline void -map_fifths_uris(LV2_URID_Map* map, FifthsURIs* uris) -{ - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); - uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); - uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); - uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); - uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); - uris->eg_applySample = map->map(map->handle, EG_FIFTHS__applySample); - uris->eg_freeSample = map->map(map->handle, EG_FIFTHS__freeSample); - uris->eg_sample = map->map(map->handle, EG_FIFTHS__sample); - uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); - uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); - uris->patch_property = map->map(map->handle, LV2_PATCH__property); - uris->patch_value = map->map(map->handle, LV2_PATCH__value); -} - -#endif /* FIFTHS_URIS_H */ diff --git a/plugins/eg06-fifths.lv2/waf b/plugins/eg06-fifths.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg06-fifths.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg06-fifths.lv2/wscript b/plugins/eg06-fifths.lv2/wscript deleted file mode 100644 index 46e2345..0000000 --- a/plugins/eg06-fifths.lv2/wscript +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-fifths.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c') - autowaf.configure(conf) - autowaf.set_c99_mode(conf) - autowaf.display_header('Fifths Configuration') - - if not autowaf.is_child(): - autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') - - autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) - print('') - -def build(bld): - bundle = 'eg-fifths.lv2' - - # Make a pattern for shared objects without the 'lib' prefix - module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) - module_ext = module_pat[module_pat.rfind('.'):] - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = '%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = module_ext) - - # Copy other data files to build bundle (build/eg-fifths.lv2) - for i in ['fifths.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = '%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Use LV2 headers from parent directory if building as a sub-project - includes = ['.'] - if autowaf.is_child: - includes += ['../..'] - - # Build plugin library - obj = bld(features = 'c cshlib', - source = 'fifths.c', - name = 'fifths', - target = '%s/fifths' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'SNDFILE LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/wscript b/plugins/wscript index c3ead30..f099183 100644 --- a/plugins/wscript +++ b/plugins/wscript @@ -18,7 +18,12 @@ def bld_book_src(task): def build(bld): files = [bld.path.find_node('README.txt')] - for i in bld.path.ant_glob('*', src=False, dir=True): + for i in ['eg-amp.lv2', + 'eg-midigate.lv2', + 'eg-fifths.lv2', + 'eg-metro.lv2', + 'eg-sampler.lv2', + 'eg-scope.lv2']: files += bld.path.ant_glob('%s/*.txt' % i) files += bld.path.ant_glob('%s/manifest.ttl*' % i) files += bld.path.ant_glob('%s/*.ttl' % i) -- cgit v1.2.1