diff options
Diffstat (limited to 'plugins')
| -rw-r--r-- | plugins/README.txt | 2 | ||||
| -rw-r--r-- | plugins/eg-amp.lv2/README.txt (renamed from plugins/eg01-amp.lv2/README.txt) | 18 | ||||
| -rw-r--r-- | plugins/eg-amp.lv2/amp.c (renamed from plugins/eg01-amp.lv2/amp.c) | 64 | ||||
| -rw-r--r-- | plugins/eg-amp.lv2/amp.ttl (renamed from plugins/eg01-amp.lv2/amp.ttl) | 20 | ||||
| -rw-r--r-- | plugins/eg-amp.lv2/manifest.ttl.in | 68 | ||||
| l--------- | plugins/eg-amp.lv2/waf (renamed from plugins/eg01-amp.lv2/waf) | 0 | ||||
| -rw-r--r-- | plugins/eg-amp.lv2/wscript | 51 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/README.txt | 3 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/fifths.c | 193 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/fifths.ttl | 30 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/manifest.ttl.in | 8 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/uris.h | 55 | ||||
| l--------- | plugins/eg-fifths.lv2/waf (renamed from plugins/eg02-midigate.lv2/waf) | 0 | ||||
| -rw-r--r-- | plugins/eg-fifths.lv2/wscript | 49 | ||||
| -rw-r--r-- | plugins/eg-metro.lv2/README.txt (renamed from plugins/eg03-metro.lv2/README.txt) | 0 | ||||
| -rw-r--r-- | plugins/eg-metro.lv2/manifest.ttl.in (renamed from plugins/eg03-metro.lv2/manifest.ttl.in) | 0 | ||||
| -rw-r--r-- | plugins/eg-metro.lv2/metro.c (renamed from plugins/eg03-metro.lv2/metro.c) | 70 | ||||
| -rw-r--r-- | plugins/eg-metro.lv2/metro.ttl (renamed from plugins/eg03-metro.lv2/metro.ttl) | 0 | ||||
| l--------- | plugins/eg-metro.lv2/waf (renamed from plugins/eg03-metro.lv2/waf) | 0 | ||||
| -rw-r--r-- | plugins/eg-metro.lv2/wscript | 50 | ||||
| -rw-r--r-- | plugins/eg-midigate.lv2/README.txt (renamed from plugins/eg02-midigate.lv2/README.txt) | 0 | ||||
| -rw-r--r-- | plugins/eg-midigate.lv2/manifest.ttl.in (renamed from plugins/eg02-midigate.lv2/manifest.ttl.in) | 0 | ||||
| -rw-r--r-- | plugins/eg-midigate.lv2/midigate.c (renamed from plugins/eg02-midigate.lv2/midigate.c) | 69 | ||||
| -rw-r--r-- | plugins/eg-midigate.lv2/midigate.ttl (renamed from plugins/eg02-midigate.lv2/midigate.ttl) | 0 | ||||
| l--------- | plugins/eg-midigate.lv2/waf (renamed from plugins/eg04-sampler.lv2/waf) | 0 | ||||
| -rw-r--r-- | plugins/eg-midigate.lv2/wscript | 49 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/README.txt | 21 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/manifest.ttl.in | 7 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/params.c | 526 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/params.ttl | 126 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/state_map.h | 114 | ||||
| -rw-r--r-- | plugins/eg-params.lv2/wscript | 49 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/README.txt (renamed from plugins/eg04-sampler.lv2/README.txt) | 1 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/atom_sink.h | 42 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/click.wav (renamed from plugins/eg04-sampler.lv2/click.wav) | bin | 644 -> 644 bytes | |||
| -rw-r--r-- | plugins/eg-sampler.lv2/manifest.ttl.in (renamed from plugins/eg04-sampler.lv2/manifest.ttl.in) | 0 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/peaks.h | 271 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 603 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.ttl (renamed from plugins/eg04-sampler.lv2/sampler.ttl) | 17 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler_ui.c | 465 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/uris.h (renamed from plugins/eg04-sampler.lv2/uris.h) | 90 | ||||
| l--------- | plugins/eg-sampler.lv2/waf | 1 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/wscript | 64 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/README.txt (renamed from plugins/eg05-scope.lv2/README.txt) | 10 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/examploscope.c (renamed from plugins/eg05-scope.lv2/examploscope.c) | 153 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/examploscope.ttl.in (renamed from plugins/eg05-scope.lv2/examploscope.ttl.in) | 0 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/examploscope_ui.c (renamed from plugins/eg05-scope.lv2/examploscope_ui.c) | 105 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/manifest.ttl.in (renamed from plugins/eg05-scope.lv2/manifest.ttl.in) | 3 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/uris.h (renamed from plugins/eg05-scope.lv2/uris.h) | 10 | ||||
| -rw-r--r-- | plugins/eg-scope.lv2/wscript | 56 | ||||
| -rw-r--r-- | plugins/eg01-amp.lv2/manifest.ttl.in | 101 | ||||
| -rw-r--r-- | plugins/eg01-amp.lv2/wscript | 67 | ||||
| -rw-r--r-- | plugins/eg02-midigate.lv2/wscript | 65 | ||||
| -rw-r--r-- | plugins/eg03-metro.lv2/wscript | 64 | ||||
| -rw-r--r-- | plugins/eg04-sampler.lv2/sampler.c | 483 | ||||
| -rw-r--r-- | plugins/eg04-sampler.lv2/sampler_ui.c | 201 | ||||
| -rw-r--r-- | plugins/eg04-sampler.lv2/wscript | 80 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/wscript | 73 | ||||
| -rwxr-xr-x | plugins/literasc.py | 36 | ||||
| -rw-r--r-- | plugins/wscript | 19 |
60 files changed, 3303 insertions, 1419 deletions
diff --git a/plugins/README.txt b/plugins/README.txt index 7eb25d8..361460d 100644 --- a/plugins/README.txt +++ b/plugins/README.txt @@ -23,4 +23,4 @@ the first plugin describes the basics in detail. This book is compiled from plugin source code into a single document for pleasant reading and ease of reference. Each chapter corresponds to executable plugin code which can be found in the +plugins+ directory of the LV2 distribution. -If you prefer to read actual source code, all the content here is also available there, where the book text is included in comments. +If you prefer to read actual source code, all the content here is also available in the source code as comments. diff --git a/plugins/eg01-amp.lv2/README.txt b/plugins/eg-amp.lv2/README.txt index f024a4d..41683d3 100644 --- a/plugins/eg01-amp.lv2/README.txt +++ b/plugins/eg-amp.lv2/README.txt @@ -2,20 +2,18 @@ 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 contain a single `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. -Turtle is a syntax for the RDF data model, -but familiarity with RDF is not required to understand this documentation. -Generally, code is kept minimal, -and all static information is described in the data. +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 - * It is simple to work with plugin data using scripting languages, command line tools, etc. - * The standard format allow the use of existing vocabularies to describe plugins and related information - * The data inherently integrates with the web, databases, etc. - * Labels and documentation are translatable, and available to hosts for display in user interfaces + * 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/eg-amp.lv2/amp.c index dd302c3..c3ba279 100644 --- a/plugins/eg01-amp.lv2/amp.c +++ b/plugins/eg-amp.lv2/amp.c @@ -1,5 +1,5 @@ /* - Copyright 2006-2011 David Robillard <d@drobilla.net> + Copyright 2006-2016 David Robillard <d@drobilla.net> Copyright 2006 Steve Harris <steve@plugin.org.uk> Permission to use, copy, modify, and/or distribute this software for any @@ -15,10 +15,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/** Include standard C headers */ -#include <math.h> -#include <stdlib.h> - /** 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 @@ -26,7 +22,12 @@ 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" +#include "lv2/core/lv2.h" + +/** Include standard C headers */ +#include <math.h> +#include <stdint.h> +#include <stdlib.h> /** The URI is the identifier for a plugin, and how the host associates this @@ -61,7 +62,7 @@ typedef struct { } Amp; /** - The instantiate() function is called by the host to create a new plugin + 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 @@ -76,15 +77,15 @@ instantiate(const LV2_Descriptor* descriptor, const char* bundle_path, const LV2_Feature* const* features) { - Amp* amp = (Amp*)malloc(sizeof(Amp)); + Amp* amp = (Amp*)calloc(1, 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(). + 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(). @@ -110,9 +111,9 @@ connect_port(LV2_Handle instance, } /** - The activate() method is called by the host to initialise and prepare the + 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 + 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 @@ -123,10 +124,15 @@ activate(LV2_Handle instance) { } -/** Define a macro for converting a gain in dB to a coefficient */ +/** 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) -/** Process a block of audio (audio thread, must be RT safe). */ +/** + 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) { @@ -144,11 +150,11 @@ run(LV2_Handle instance, uint32_t n_samples) } /** - The deactivate() method is the counterpart to activate() called by the host - after running the plugin. It indicates that the host will not call run() - again until another call to activate() and is mainly useful for more + 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 + 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 @@ -160,7 +166,7 @@ deactivate(LV2_Handle instance) } /** - Destroy a plugin instance (counterpart to instantiate()). + 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. @@ -172,7 +178,7 @@ cleanup(LV2_Handle instance) } /** - The extension_data function returns any extension data supported by the + 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 @@ -188,9 +194,9 @@ extension_data(const char* uri) } /** - Define the LV2_Descriptor for this plugin. It is best to define descriptors - statically to avoid leaking memory and non-portable shared library - constructors and destructors to clean up properly. + 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, @@ -204,12 +210,12 @@ static const LV2_Descriptor descriptor = { }; /** - The lv2_descriptor() function is the entry point to the plugin library. The + 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. */ @@ -218,9 +224,7 @@ const LV2_Descriptor* lv2_descriptor(uint32_t index) { switch (index) { - case 0: - return &descriptor; - default: - return NULL; + case 0: return &descriptor; + default: return NULL; } } diff --git a/plugins/eg01-amp.lv2/amp.ttl b/plugins/eg-amp.lv2/amp.ttl index f4a87f2..9f522a1 100644 --- a/plugins/eg01-amp.lv2/amp.ttl +++ b/plugins/eg-amp.lv2/amp.ttl @@ -2,10 +2,11 @@ # `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: <http://usefulinc.com/ns/doap#> . -@prefix lv2: <http://lv2plug.in/ns/lv2core#> . -@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . -@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix units: <http://lv2plug.in/ns/extensions/units#> . # 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 @@ -25,9 +26,9 @@ # Translations to various languages can be added by putting a language tag # after strings as shown. doap:name "Simple Amplifier" , - "简单放大器"@ch , + "简单放大器"@zh , "Einfacher Verstärker"@de , - "Simple Amp"@en-gb , + "Simple Amplifier"@en-gb , "Amplificador Simple"@es , "Amplificateur de Base"@fr , "Amplificatore Semplice"@it , @@ -44,8 +45,8 @@ lv2:index 0 ; lv2:symbol "gain" ; lv2:name "Gain" , - "收益"@ch , - "Gewinn"@de , + "收益"@zh , + "Verstärkung"@de , "Gain"@en-gb , "Aumento"@es , "Gain"@fr , @@ -58,6 +59,9 @@ lv2:default 0.0 ; lv2:minimum -90.0 ; lv2:maximum 24.0 ; +# Ports can describe units and control detents to allow better UI generation +# and host automation. + units:unit units:db ; lv2:scalePoint [ rdfs:label "+5" ; rdf:value 5.0 diff --git a/plugins/eg-amp.lv2/manifest.ttl.in b/plugins/eg-amp.lv2/manifest.ttl.in new file mode 100644 index 0000000..4a22f95 --- /dev/null +++ b/plugins/eg-amp.lv2/manifest.ttl.in @@ -0,0 +1,68 @@ +# LV2 plugins are installed in a ``bundle'', a directory with a standard +# structure. Each bundle has a Turtle file named `manifest.ttl` which lists +# the contents of the bundle. +# +# Hosts typically read the manifest of every installed bundle to discover +# plugins on start-up, so it should be as small as possible for performance +# reasons. Details that are only useful if the host chooses to load the plugin +# are stored in other files and linked to from `manifest.ttl`. +# +# ==== URIs ==== +# +# LV2 makes use of URIs as globally-unique identifiers for resources. For +# example, the ID of the plugin described here is +# `<http://lv2plug.in/plugins/eg-amp>`. Note that URIs are only used as +# identifiers and don't necessarily imply that something can be accessed at +# that address on the web (though that may be the case). +# +# ==== Namespace Prefixes ==== +# +# Turtle files contain many URIs, but prefixes can be defined to improve +# readability. For example, with the `lv2:` prefix below, `lv2:Plugin` can be +# written instead of `<http://lv2plug.in/ns/lv2core#Plugin>`. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +# ==== Describing a Plugin ==== + +# Turtle files contain a set of ``statements'' which describe resources. +# This file contains 3 statements: +# [options="header"] +# |================================================================ +# | Subject | Predicate | Object +# | <http://lv2plug.in/plugins/eg-amp> | a | lv2:Plugin +# | <http://lv2plug.in/plugins/eg-amp> | lv2:binary | <amp.so> +# | <http://lv2plug.in/plugins/eg-amp> | rdfs:seeAlso | <amp.ttl> +# |================================================================ + +# Firstly, `<http://lv2plug.in/plugins/eg-amp>` is an LV2 plugin: +<http://lv2plug.in/plugins/eg-amp> a lv2:Plugin . + +# The predicate ```a`'' is a Turtle shorthand for `rdf:type`. + +# The binary of that plugin can be found at `<amp.ext>`: +<http://lv2plug.in/plugins/eg-amp> lv2:binary <amp@LIB_EXT@> . + +# This file is a template; the token `@LIB_EXT@` is replaced by the build +# system with the appropriate extension for the current platform before +# installation. For example, in the output `manifest.ttl`, the binary would be +# listed as `<amp.so>`. Relative URIs in manifests are relative to the bundle +# directory, so this refers to a binary with the given name in the same +# directory as this manifest. + +# Finally, more information about this plugin can be found in `<amp.ttl>`: +<http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl> . + +# ==== Abbreviation ==== +# +# This file shows these statements individually for instructive purposes, but +# the subject `<http://lv2plug.in/plugins/eg-amp>` is repetitive. Turtle +# allows the semicolon to be used as a delimiter that repeats the previous +# subject. For example, this manifest would more realistically be written like +# so: + +<http://lv2plug.in/plugins/eg-amp> + a lv2:Plugin ; + lv2:binary <amp@LIB_EXT@> ; + rdfs:seeAlso <amp.ttl> . diff --git a/plugins/eg01-amp.lv2/waf b/plugins/eg-amp.lv2/waf index 59a1ac9..59a1ac9 120000 --- a/plugins/eg01-amp.lv2/waf +++ b/plugins/eg-amp.lv2/waf diff --git a/plugins/eg-amp.lv2/wscript b/plugins/eg-amp.lv2/wscript new file mode 100644 index 0000000..822825d --- /dev/null +++ b/plugins/eg-amp.lv2/wscript @@ -0,0 +1,51 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2', uselib_store='LV2') + + conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) + +def build(bld): + bundle = 'eg-amp.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_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 = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'amp.c', + name = 'amp', + target = 'lv2/%s/amp' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'M LV2') 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..5782417 --- /dev/null +++ b/plugins/eg-fifths.lv2/fifths.c @@ -0,0 +1,193 @@ +/* + LV2 Fifths Example Plugin + Copyright 2014-2016 David Robillard <d@drobilla.net> + + 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 "./uris.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/urid/urid.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +enum { + FIFTHS_IN = 0, + FIFTHS_OUT = 1 +}; + +typedef struct { + // Features + LV2_URID_Map* map; + LV2_Log_Logger logger; + + // 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*)calloc(1, sizeof(Fifths)); + if (!self) { + return NULL; + } + + // Scan host features for URID map + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + free(self); + return NULL; + } + + 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*)LV2_ATOM_BODY_CONST(ev); + 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); + + if (msg[1] <= 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: <http://lv2plug.in/ns/ext/atom#> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix midi: <http://lv2plug.in/ns/ext/midi#> . + +<http://lv2plug.in/plugins/eg-fifths> + a lv2:Plugin ; + doap:name "Example Fifths" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:project <http://lv2plug.in/ns/lv2> ; + 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: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://lv2plug.in/plugins/eg-fifths> + a lv2:Plugin ; + lv2:binary <fifths@LIB_EXT@> ; + rdfs:seeAlso <fifths.ttl> . diff --git a/plugins/eg-fifths.lv2/uris.h b/plugins/eg-fifths.lv2/uris.h new file mode 100644 index 0000000..04b09f6 --- /dev/null +++ b/plugins/eg-fifths.lv2/uris.h @@ -0,0 +1,55 @@ +/* + LV2 Fifths Example Plugin + Copyright 2014-2015 David Robillard <d@drobilla.net> + + 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/atom/atom.h" +#include "lv2/log/log.h" +#include "lv2/midi/midi.h" +#include "lv2/patch/patch.h" +#include "lv2/state/state.h" + +#define EG_FIFTHS_URI "http://lv2plug.in/plugins/eg-fifths" + +typedef struct { + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + 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_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->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/eg02-midigate.lv2/waf b/plugins/eg-fifths.lv2/waf index 59a1ac9..59a1ac9 120000 --- a/plugins/eg02-midigate.lv2/waf +++ b/plugins/eg-fifths.lv2/waf diff --git a/plugins/eg-fifths.lv2/wscript b/plugins/eg-fifths.lv2/wscript new file mode 100644 index 0000000..8b2991b --- /dev/null +++ b/plugins/eg-fifths.lv2/wscript @@ -0,0 +1,49 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') + +def build(bld): + bundle = 'eg-fifths.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_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 = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'fifths.c', + name = 'fifths', + target = 'lv2/%s/fifths' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2') diff --git a/plugins/eg03-metro.lv2/README.txt b/plugins/eg-metro.lv2/README.txt index 5e9a84a..5e9a84a 100644 --- a/plugins/eg03-metro.lv2/README.txt +++ b/plugins/eg-metro.lv2/README.txt diff --git a/plugins/eg03-metro.lv2/manifest.ttl.in b/plugins/eg-metro.lv2/manifest.ttl.in index bd93f66..bd93f66 100644 --- a/plugins/eg03-metro.lv2/manifest.ttl.in +++ b/plugins/eg-metro.lv2/manifest.ttl.in diff --git a/plugins/eg03-metro.lv2/metro.c b/plugins/eg-metro.lv2/metro.c index 51125af..a7231d2 100644 --- a/plugins/eg03-metro.lv2/metro.c +++ b/plugins/eg-metro.lv2/metro.c @@ -1,6 +1,6 @@ /* LV2 Metronome Example Plugin - Copyright 2012 David Robillard <d@drobilla.net> + Copyright 2012-2016 David Robillard <d@drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,19 +15,21 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "lv2/atom/atom.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/time/time.h" +#include "lv2/urid/urid.h" + #include <math.h> +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#ifndef __cplusplus -# include <stdbool.h> -#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 @@ -38,6 +40,7 @@ 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; @@ -68,14 +71,15 @@ typedef enum { 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 + LV2_URID_Map* map; // URID map feature + LV2_Log_Logger logger; // Logger API + MetroURIs uris; // Cache of mapped URIDs struct { LV2_Atom_Sequence* control; @@ -151,23 +155,24 @@ instantiate(const LV2_Descriptor* descriptor, } // 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"); + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); free(self); return NULL; } // Map URIS - MetroURIs* const uris = &self->uris; - self->map = map; + MetroURIs* const uris = &self->uris; + LV2_URID_Map* const map = self->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); @@ -179,17 +184,17 @@ instantiate(const LV2_Descriptor* descriptor, // Initialise instance fields self->rate = rate; self->bpm = 120.0f; - self->attack_len = attack_s * rate; - self->decay_len = decay_s * rate; + 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 = rate / freq; + 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] = sin(i * 2 * M_PI * freq / rate) * amp; + self->wave[i] = (float)(sin(i * 2 * M_PI * freq / rate) * amp); } return (LV2_Handle)self; @@ -301,15 +306,18 @@ run(LV2_Handle instance, uint32_t sample_count) // 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 (LV2_Atom_Event* ev = lv2_atom_sequence_begin(&in->body); + 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); - if (ev->body.type == uris->atom_Blank) { - const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body; + // 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); diff --git a/plugins/eg03-metro.lv2/metro.ttl b/plugins/eg-metro.lv2/metro.ttl index 8b4af3d..8b4af3d 100644 --- a/plugins/eg03-metro.lv2/metro.ttl +++ b/plugins/eg-metro.lv2/metro.ttl diff --git a/plugins/eg03-metro.lv2/waf b/plugins/eg-metro.lv2/waf index 59a1ac9..59a1ac9 120000 --- a/plugins/eg03-metro.lv2/waf +++ b/plugins/eg-metro.lv2/waf diff --git a/plugins/eg-metro.lv2/wscript b/plugins/eg-metro.lv2/wscript new file mode 100644 index 0000000..5fb0d07 --- /dev/null +++ b/plugins/eg-metro.lv2/wscript @@ -0,0 +1,50 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2 >= 0.2.0', uselib_store='LV2') + + conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) + +def build(bld): + bundle = 'eg-metro.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_EXT) + + # Copy other data files to build bundle (build/eg-metro.lv2) + bld(features = 'subst', + is_copy = True, + source = 'metro.ttl', + target = 'lv2/%s/metro.ttl' % bundle, + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'metro.c', + name = 'metro', + target = 'lv2/%s/metro' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = ['M', 'LV2']) diff --git a/plugins/eg02-midigate.lv2/README.txt b/plugins/eg-midigate.lv2/README.txt index 8f4a0f0..8f4a0f0 100644 --- a/plugins/eg02-midigate.lv2/README.txt +++ b/plugins/eg-midigate.lv2/README.txt diff --git a/plugins/eg02-midigate.lv2/manifest.ttl.in b/plugins/eg-midigate.lv2/manifest.ttl.in index d32f1dc..d32f1dc 100644 --- a/plugins/eg02-midigate.lv2/manifest.ttl.in +++ b/plugins/eg-midigate.lv2/manifest.ttl.in diff --git a/plugins/eg02-midigate.lv2/midigate.c b/plugins/eg-midigate.lv2/midigate.c index 3ed6fbf..6c77828 100644 --- a/plugins/eg02-midigate.lv2/midigate.c +++ b/plugins/eg-midigate.lv2/midigate.c @@ -1,5 +1,5 @@ /* - Copyright 2013 David Robillard <d@drobilla.net> + Copyright 2013-2016 David Robillard <d@drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -14,16 +14,20 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "lv2/atom/atom.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/urid/urid.h" + +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> - -#include <math.h> #include <stdlib.h> - -#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" +#include <string.h> #define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" @@ -40,7 +44,8 @@ typedef struct { float* out; // Features - LV2_URID_Map* map; + LV2_URID_Map* map; + LV2_Log_Logger logger; struct { LV2_URID midi_MidiEvent; @@ -56,25 +61,26 @@ instantiate(const LV2_Descriptor* descriptor, 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; - } + Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); + if (!self) { + return NULL; } - if (!map) { - /** - No URID feature given. This is a host bug since we require this - feature, but should be handled gracefully anyway. - */ + + // Scan host features for URID map + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + free(self); return NULL; } - Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); - self->map = map; - self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); + self->uris.midi_MidiEvent = self->map->map( + self->map->handle, LV2_MIDI__MidiEvent); return (LV2_Handle)self; } @@ -155,13 +161,20 @@ run(LV2_Handle instance, uint32_t sample_count) 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); + const uint8_t* const msg = (const uint8_t*)LV2_ATOM_BODY_CONST(ev); 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; + if (self->n_active_notes > 0) { + --self->n_active_notes; + } + break; + case LV2_MIDI_MSG_CONTROLLER: + if (msg[1] == LV2_MIDI_CTL_ALL_NOTES_OFF) { + self->n_active_notes = 0; + } break; case LV2_MIDI_MSG_PGM_CHANGE: if (msg[1] == 0 || msg[1] == 1) { @@ -173,7 +186,7 @@ run(LV2_Handle instance, uint32_t sample_count) } write_output(self, offset, ev->time.frames - offset); - offset = ev->time.frames; + offset = (uint32_t)ev->time.frames; } write_output(self, offset, sample_count - offset); diff --git a/plugins/eg02-midigate.lv2/midigate.ttl b/plugins/eg-midigate.lv2/midigate.ttl index e14a329..e14a329 100644 --- a/plugins/eg02-midigate.lv2/midigate.ttl +++ b/plugins/eg-midigate.lv2/midigate.ttl diff --git a/plugins/eg04-sampler.lv2/waf b/plugins/eg-midigate.lv2/waf index 59a1ac9..59a1ac9 120000 --- a/plugins/eg04-sampler.lv2/waf +++ b/plugins/eg-midigate.lv2/waf diff --git a/plugins/eg-midigate.lv2/wscript b/plugins/eg-midigate.lv2/wscript new file mode 100644 index 0000000..5862721 --- /dev/null +++ b/plugins/eg-midigate.lv2/wscript @@ -0,0 +1,49 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2', uselib_store='LV2') + +def build(bld): + bundle = 'eg-midigate.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_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 = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'midigate.c', + name = 'midigate', + target = 'lv2/%s/midigate' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'LV2') diff --git a/plugins/eg-params.lv2/README.txt b/plugins/eg-params.lv2/README.txt new file mode 100644 index 0000000..acf90c1 --- /dev/null +++ b/plugins/eg-params.lv2/README.txt @@ -0,0 +1,21 @@ +== Params == + +The basic LV2 mechanism for controls is +http://lv2plug.in/ns/lv2core#ControlPort[lv2:ControlPort], inherited from +LADSPA. Control ports are problematic because they are not sample accurate, +support only one type (`float`), and require that plugins poll to know when a +control has changed. + +Parameters can be used instead to address these issues. Parameters can be +thought of as properties of a plugin instance; they are identified by URI and +have a value of any type. This deliberately meshes with the concept of plugin +state defined by the http://lv2plug.in/ns/ext/state[LV2 state extension]. +The state extension allows plugins to save and restore their parameters (along +with other internal state information, if necessary). + +Parameters are accessed and manipulated using messages sent via a sequence +port. The http://lv2plug.in/ns/ext/patch[LV2 patch extension] defines the +standard messages for working with parameters. Typically, only two are used +for simple plugins: http://lv2plug.in/ns/ext/patch#Set[patch:Set] sets a +parameter to some value, and http://lv2plug.in/ns/ext/patch#Get[patch:Get] +requests that the plugin send a description of its parameters. diff --git a/plugins/eg-params.lv2/manifest.ttl.in b/plugins/eg-params.lv2/manifest.ttl.in new file mode 100644 index 0000000..913de7c --- /dev/null +++ b/plugins/eg-params.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://lv2plug.in/plugins/eg-params> + a lv2:Plugin ; + lv2:binary <params@LIB_EXT@> ; + rdfs:seeAlso <params.ttl> . diff --git a/plugins/eg-params.lv2/params.c b/plugins/eg-params.lv2/params.c new file mode 100644 index 0000000..94d34a5 --- /dev/null +++ b/plugins/eg-params.lv2/params.c @@ -0,0 +1,526 @@ +/* + LV2 Parameter Example Plugin + Copyright 2014-2016 David Robillard <d@drobilla.net> + + 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 "state_map.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/patch/patch.h" +#include "lv2/state/state.h" +#include "lv2/urid/urid.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MAX_STRING 1024 + +#define EG_PARAMS_URI "http://lv2plug.in/plugins/eg-params" + +#define N_PROPS 9 + +typedef struct { + LV2_URID plugin; + LV2_URID atom_Path; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + LV2_URID eg_spring; + LV2_URID midi_Event; + LV2_URID patch_Get; + LV2_URID patch_Set; + LV2_URID patch_Put; + LV2_URID patch_body; + LV2_URID patch_subject; + LV2_URID patch_property; + LV2_URID patch_value; +} URIs; + +typedef struct { + LV2_Atom_Int aint; + LV2_Atom_Long along; + LV2_Atom_Float afloat; + LV2_Atom_Double adouble; + LV2_Atom_Bool abool; + LV2_Atom astring; + char string[MAX_STRING]; + LV2_Atom apath; + char path[MAX_STRING]; + LV2_Atom_Float lfo; + LV2_Atom_Float spring; +} State; + +static inline void +map_uris(LV2_URID_Map* map, URIs* uris) +{ + uris->plugin = map->map(map->handle, EG_PARAMS_URI); + + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + 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_spring = map->map(map->handle, EG_PARAMS_URI "#spring"); + uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); + uris->patch_Get = map->map(map->handle, LV2_PATCH__Get); + uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris->patch_Put = map->map(map->handle, LV2_PATCH__Put); + uris->patch_body = map->map(map->handle, LV2_PATCH__body); + uris->patch_subject = map->map(map->handle, LV2_PATCH__subject); + uris->patch_property = map->map(map->handle, LV2_PATCH__property); + uris->patch_value = map->map(map->handle, LV2_PATCH__value); +} + +enum { + PARAMS_IN = 0, + PARAMS_OUT = 1 +}; + +typedef struct { + // Features + LV2_URID_Map* map; + LV2_URID_Unmap* unmap; + LV2_Log_Logger log; + + // Forge for creating atoms + LV2_Atom_Forge forge; + + // Ports + const LV2_Atom_Sequence* in_port; + LV2_Atom_Sequence* out_port; + + // URIs + URIs uris; + + // Plugin state + StateMapItem props[N_PROPS]; + State state; + + // Buffer for making strings from URIDs if unmap is not provided + char urid_buf[12]; +} Params; + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Params* self = (Params*)instance; + switch (port) { + case PARAMS_IN: + self->in_port = (const LV2_Atom_Sequence*)data; + break; + case PARAMS_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 instance + Params* self = (Params*)calloc(1, sizeof(Params)); + if (!self) { + return NULL; + } + + // Get host features + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->log.log, false, + LV2_URID__map, &self->map, true, + LV2_URID__unmap, &self->unmap, false, + NULL); + lv2_log_logger_set_map(&self->log, self->map); + if (missing) { + lv2_log_error(&self->log, "Missing feature <%s>\n", missing); + free(self); + return NULL; + } + + // Map URIs and initialise forge + map_uris(self->map, &self->uris); + lv2_atom_forge_init(&self->forge, self->map); + + // Initialise state dictionary + State* state = &self->state; + state_map_init( + self->props, self->map, self->map->handle, + EG_PARAMS_URI "#int", STATE_MAP_INIT(Int, &state->aint), + EG_PARAMS_URI "#long", STATE_MAP_INIT(Long, &state->along), + EG_PARAMS_URI "#float", STATE_MAP_INIT(Float, &state->afloat), + EG_PARAMS_URI "#double", STATE_MAP_INIT(Double, &state->adouble), + EG_PARAMS_URI "#bool", STATE_MAP_INIT(Bool, &state->abool), + EG_PARAMS_URI "#string", STATE_MAP_INIT(String, &state->astring), + EG_PARAMS_URI "#path", STATE_MAP_INIT(Path, &state->apath), + EG_PARAMS_URI "#lfo", STATE_MAP_INIT(Float, &state->lfo), + EG_PARAMS_URI "#spring", STATE_MAP_INIT(Float, &state->spring), + NULL); + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** Helper function to unmap a URID if possible. */ +static const char* +unmap(Params* self, LV2_URID urid) +{ + if (self->unmap) { + return self->unmap->unmap(self->unmap->handle, urid); + } else { + snprintf(self->urid_buf, sizeof(self->urid_buf), "%u", urid); + return self->urid_buf; + } +} + +static LV2_State_Status +check_type(Params* self, + LV2_URID key, + LV2_URID type, + LV2_URID required_type) +{ + if (type != required_type) { + lv2_log_trace( + &self->log, "Bad type <%s> for <%s> (needs <%s>)\n", + unmap(self, type), + unmap(self, key), + unmap(self, required_type)); + return LV2_STATE_ERR_BAD_TYPE; + } + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +set_parameter(Params* self, + LV2_URID key, + uint32_t size, + LV2_URID type, + const void* body, + bool from_state) +{ + // Look up property in state dictionary + const StateMapItem* entry = state_map_find(self->props, N_PROPS, key); + if (!entry) { + lv2_log_trace(&self->log, "Unknown parameter <%s>\n", unmap(self, key)); + return LV2_STATE_ERR_NO_PROPERTY; + } + + // Ensure given type matches property's type + if (check_type(self, key, type, entry->value->type)) { + return LV2_STATE_ERR_BAD_TYPE; + } + + // Set property value in state dictionary + lv2_log_trace(&self->log, "Set <%s>\n", entry->uri); + memcpy(entry->value + 1, body, size); + entry->value->size = size; + return LV2_STATE_SUCCESS; +} + +static const LV2_Atom* +get_parameter(Params* self, LV2_URID key) +{ + const StateMapItem* entry = state_map_find(self->props, N_PROPS, key); + if (entry) { + lv2_log_trace(&self->log, "Get <%s>\n", entry->uri); + return entry->value; + } + + lv2_log_trace(&self->log, "Unknown parameter <%s>\n", unmap(self, key)); + return NULL; +} + +static LV2_State_Status +write_param_to_forge(LV2_State_Handle handle, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + uint32_t flags) +{ + LV2_Atom_Forge* forge = (LV2_Atom_Forge*)handle; + + if (!lv2_atom_forge_key(forge, key) || + !lv2_atom_forge_atom(forge, size, type) || + !lv2_atom_forge_write(forge, value, size)) { + return LV2_STATE_ERR_UNKNOWN; + } + + return LV2_STATE_SUCCESS; +} + +static void +store_prop(Params* self, + LV2_State_Map_Path* map_path, + LV2_State_Status* save_status, + LV2_State_Store_Function store, + LV2_State_Handle handle, + LV2_URID key, + const LV2_Atom* value) +{ + LV2_State_Status st; + if (map_path && value->type == self->uris.atom_Path) { + // Map path to abstract path for portable storage + const char* path = (const char*)(value + 1); + char* apath = map_path->abstract_path(map_path->handle, path); + st = store(handle, + key, + apath, + strlen(apath) + 1, + self->uris.atom_Path, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + free(apath); + } else { + // Store simple property + st = store(handle, + key, + value + 1, + value->size, + value->type, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + } + + if (save_status && !*save_status) { + *save_status = st; + } +} + +/** + State save method. + + This is used in the usual way when called by the host to save plugin state, + but also internally for writing messages in the audio thread by passing a + "store" function which actually writes the description to the forge. +*/ +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) +{ + Params* self = (Params*)instance; + LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)lv2_features_data( + features, LV2_STATE__mapPath); + + LV2_State_Status st = LV2_STATE_SUCCESS; + for (unsigned i = 0; i < N_PROPS; ++i) { + StateMapItem* prop = &self->props[i]; + store_prop(self, map_path, &st, store, handle, prop->urid, prop->value); + } + + return st; +} + +static void +retrieve_prop(Params* self, + LV2_State_Status* restore_status, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + LV2_URID key) +{ + // Retrieve value from saved state + size_t vsize; + uint32_t vtype; + uint32_t vflags; + const void* value = retrieve(handle, key, &vsize, &vtype, &vflags); + + // Set plugin instance state + const LV2_State_Status st = value + ? set_parameter(self, key, vsize, vtype, value, true) + : LV2_STATE_ERR_NO_PROPERTY; + + if (!*restore_status) { + *restore_status = st; // Set status if there has been no error yet + } +} + +/** State restore method. */ +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) +{ + Params* self = (Params*)instance; + LV2_State_Status st = LV2_STATE_SUCCESS; + + for (unsigned i = 0; i < N_PROPS; ++i) { + retrieve_prop(self, &st, retrieve, handle, self->props[i].urid); + } + + return st; +} + +static inline bool +subject_is_plugin(Params* self, const LV2_Atom_URID* subject) +{ + // This simple plugin only supports one subject: itself + return (!subject || (subject->atom.type == self->uris.atom_URID && + subject->body == self->uris.plugin)); +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Params* self = (Params*)instance; + URIs* uris = &self->uris; + + // Initially, self->out_port contains a Chunk with size set to capacity + // Set up forge to write directly to output port + const uint32_t out_capacity = self->out_port->atom.size; + lv2_atom_forge_set_buffer(&self->forge, + (uint8_t*)self->out_port, + out_capacity); + + // Start a sequence in the output port + LV2_Atom_Forge_Frame out_frame; + lv2_atom_forge_sequence_head(&self->forge, &out_frame, 0); + + // Read incoming events + LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; + if (obj->body.otype == uris->patch_Set) { + // Get the property and value of the set message + const LV2_Atom_URID* subject = NULL; + const LV2_Atom_URID* property = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + uris->patch_subject, (const LV2_Atom**)&subject, + uris->patch_property, (const LV2_Atom**)&property, + uris->patch_value, &value, + 0); + if (!subject_is_plugin(self, subject)) { + lv2_log_error(&self->log, "Set for unknown subject\n"); + } else if (!property) { + lv2_log_error(&self->log, "Set with no property\n"); + } else if (property->atom.type != uris->atom_URID) { + lv2_log_error(&self->log, "Set property is not a URID\n"); + } else { + // Set property to the given value + const LV2_URID key = property->body; + set_parameter(self, key, value->size, value->type, value + 1, false); + } + } else if (obj->body.otype == uris->patch_Get) { + // Get the property of the get message + const LV2_Atom_URID* subject = NULL; + const LV2_Atom_URID* property = NULL; + lv2_atom_object_get(obj, + uris->patch_subject, (const LV2_Atom**)&subject, + uris->patch_property, (const LV2_Atom**)&property, + 0); + if (!subject_is_plugin(self, subject)) { + lv2_log_error(&self->log, "Get with unknown subject\n"); + } else if (!property) { + // Get with no property, emit complete state + lv2_atom_forge_frame_time(&self->forge, ev->time.frames); + LV2_Atom_Forge_Frame pframe; + lv2_atom_forge_object(&self->forge, &pframe, 0, uris->patch_Put); + lv2_atom_forge_key(&self->forge, uris->patch_body); + + LV2_Atom_Forge_Frame bframe; + lv2_atom_forge_object(&self->forge, &bframe, 0, 0); + save(self, write_param_to_forge, &self->forge, 0, NULL); + + lv2_atom_forge_pop(&self->forge, &bframe); + lv2_atom_forge_pop(&self->forge, &pframe); + } else if (property->atom.type != uris->atom_URID) { + lv2_log_error(&self->log, "Get property is not a URID\n"); + } else { + // Get for a specific property + const LV2_URID key = property->body; + const LV2_Atom* value = get_parameter(self, key); + if (value) { + lv2_atom_forge_frame_time(&self->forge, ev->time.frames); + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(&self->forge, &frame, 0, uris->patch_Set); + lv2_atom_forge_key(&self->forge, uris->patch_property); + lv2_atom_forge_urid(&self->forge, property->body); + store_prop(self, NULL, NULL, write_param_to_forge, &self->forge, + uris->patch_value, value); + lv2_atom_forge_pop(&self->forge, &frame); + } + } + } else { + lv2_log_trace(&self->log, "Unknown object type <%s>\n", + unmap(self, obj->body.otype)); + } + } + + if (self->state.spring.body > 0.0f) { + const float spring = self->state.spring.body; + self->state.spring.body = (spring >= 0.001) ? spring - 0.001 : 0.0; + lv2_atom_forge_frame_time(&self->forge, 0); + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(&self->forge, &frame, 0, uris->patch_Set); + + lv2_atom_forge_key(&self->forge, uris->patch_property); + lv2_atom_forge_urid(&self->forge, uris->eg_spring); + lv2_atom_forge_key(&self->forge, uris->patch_value); + lv2_atom_forge_float(&self->forge, self->state.spring.body); + + lv2_atom_forge_pop(&self->forge, &frame); + } + + lv2_atom_forge_pop(&self->forge, &out_frame); +} + +static const void* +extension_data(const char* uri) +{ + static const LV2_State_Interface state = { save, restore }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } + return NULL; +} + +static const LV2_Descriptor descriptor = { + EG_PARAMS_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/plugins/eg-params.lv2/params.ttl b/plugins/eg-params.lv2/params.ttl new file mode 100644 index 0000000..931c826 --- /dev/null +++ b/plugins/eg-params.lv2/params.ttl @@ -0,0 +1,126 @@ +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix param: <http://lv2plug.in/ns/ext/parameters#> . +@prefix patch: <http://lv2plug.in/ns/ext/patch#> . +@prefix plug: <http://lv2plug.in/plugins/eg-params#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix state: <http://lv2plug.in/ns/ext/state#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . + +# An existing parameter or RDF property can be used as a parameter. The LV2 +# parameters extension <http://lv2plug.in/ns/ext/parameters> defines many +# common audio parameters. Where possible, existing parameters should be used +# so hosts can intelligently control plugins. + +# If no suitable parameter exists, one can be defined for the plugin like so: + +plug:int + a lv2:Parameter ; + rdfs:label "int" ; + rdfs:range atom:Int . + +plug:long + a lv2:Parameter ; + rdfs:label "long" ; + rdfs:range atom:Long . + +plug:float + a lv2:Parameter ; + rdfs:label "float" ; + rdfs:range atom:Float . + +plug:double + a lv2:Parameter ; + rdfs:label "double" ; + rdfs:range atom:Double . + +plug:bool + a lv2:Parameter ; + rdfs:label "bool" ; + rdfs:range atom:Bool . + +plug:string + a lv2:Parameter ; + rdfs:label "string" ; + rdfs:range atom:String . + +plug:path + a lv2:Parameter ; + rdfs:label "path" ; + rdfs:range atom:Path . + +plug:lfo + a lv2:Parameter ; + rdfs:label "LFO" ; + rdfs:range atom:Float ; + lv2:minimum -1.0 ; + lv2:maximum 1.0 . + +plug:spring + a lv2:Parameter ; + rdfs:label "spring" ; + rdfs:range atom:Float . + +# Most of the plugin description is similar to the others we have seen, but +# this plugin has only two ports, for receiving and sending messages used to +# manipulate and access parameters. +<http://lv2plug.in/plugins/eg-params> + a lv2:Plugin , + lv2:UtilityPlugin ; + doap:name "Example Parameters" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:project <http://lv2plug.in/ns/lv2> ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable , + state:loadDefaultState ; + lv2:extensionData state:interface ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "out" ; + lv2:name "Out" + ] ; +# The plugin must list all parameters that can be written (e.g. changed by the +# user) as patch:writable: + patch:writable plug:int , + plug:long , + plug:float , + plug:double , + plug:bool , + plug:string , + plug:path , + plug:spring ; +# Similarly, parameters that may change internally must be listed as patch:readable, +# meaning to host should watch for changes to the parameter's value: + patch:readable plug:lfo , + plug:spring ; +# Parameters map directly to properties of the plugin's state. So, we can +# specify initial parameter values with the state:state property. The +# state:loadDefaultState feature (required above) requires that the host loads +# the default state after instantiation but before running the plugin. + state:state [ + plug:int 0 ; + plug:long "0"^^xsd:long ; + plug:float "0.1234"^^xsd:float ; + plug:double "0e0"^^xsd:double ; + plug:bool false ; + plug:string "Hello, world" ; + plug:path <params.ttl> ; + plug:spring "0.0"^^xsd:float ; + plug:lfo "0.0"^^xsd:float + ] . diff --git a/plugins/eg-params.lv2/state_map.h b/plugins/eg-params.lv2/state_map.h new file mode 100644 index 0000000..c80d4a2 --- /dev/null +++ b/plugins/eg-params.lv2/state_map.h @@ -0,0 +1,114 @@ +/* + LV2 State Map + Copyright 2016 David Robillard <d@drobilla.net> + + 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 "lv2/atom/atom.h" +#include "lv2/urid/urid.h" + +#include <stdarg.h> +#include <stdlib.h> + +/** Entry in an array that serves as a dictionary of properties. */ +typedef struct { + const char* uri; + LV2_URID urid; + LV2_Atom* value; +} StateMapItem; + +/** Comparator for StateMapItems sorted by URID. */ +static int +state_map_cmp(const void* a, const void* b) +{ + const StateMapItem* ka = (const StateMapItem*)a; + const StateMapItem* kb = (const StateMapItem*)b; + if (ka->urid < kb->urid) { + return -1; + } else if (kb->urid < ka->urid) { + return 1; + } + return 0; +} + +/** Helper macro for terse state map initialisation. */ +#define STATE_MAP_INIT(type, ptr) \ + (LV2_ATOM__ ## type), \ + (sizeof(*ptr) - sizeof(LV2_Atom)), \ + (ptr) + +/** + Initialise a state map. + + The variable parameters list must be NULL terminated, and is a sequence of + const char* uri, const char* type, uint32_t size, LV2_Atom* value. The + value must point to a valid atom that resides elsewhere, the state map is + only an index and does not contain actual state values. The macro + STATE_MAP_INIT can be used to make simpler code when state is composed of + standard atom types, for example: + + struct Plugin { + LV2_URID_Map* map; + StateMapItem props[3]; + // ... + }; + + state_map_init( + self->props, self->map, self->map->handle, + PLUG_URI "#gain", STATE_MAP_INIT(Float, &state->gain), + PLUG_URI "#offset", STATE_MAP_INIT(Int, &state->offset), + PLUG_URI "#file", STATE_MAP_INIT(Path, &state->file), + NULL); +*/ +static void +state_map_init(StateMapItem dict[], + LV2_URID_Map* map, + LV2_URID_Map_Handle handle, + /* const char* uri, const char* type, uint32_t size, LV2_Atom* value */ ...) +{ + // Set dict entries from parameters + unsigned i = 0; + va_list args; + va_start(args, handle); + for (const char* uri; (uri = va_arg(args, const char*)); ++i) { + const char* type = va_arg(args, const char*); + const uint32_t size = va_arg(args, uint32_t); + LV2_Atom* const value = va_arg(args, LV2_Atom*); + dict[i].uri = uri; + dict[i].urid = map->map(map->handle, uri); + dict[i].value = value; + dict[i].value->size = size; + dict[i].value->type = map->map(map->handle, type); + } + va_end(args); + + // Sort for fast lookup by URID by state_map_find() + qsort(dict, i, sizeof(StateMapItem), state_map_cmp); +} + +/** + Retrieve an item from a state map by URID. + + This takes O(lg(n)) time, and is useful for implementing generic property + access with little code, for example to respond to patch:Get messages for a + specific property. +*/ +static StateMapItem* +state_map_find(StateMapItem dict[], uint32_t n_entries, LV2_URID urid) +{ + const StateMapItem key = { NULL, urid, NULL }; + return (StateMapItem*)bsearch( + &key, dict, n_entries, sizeof(StateMapItem), state_map_cmp); +} + diff --git a/plugins/eg-params.lv2/wscript b/plugins/eg-params.lv2/wscript new file mode 100644 index 0000000..503e8db --- /dev/null +++ b/plugins/eg-params.lv2/wscript @@ -0,0 +1,49 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-params.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2 >= 1.12.1', uselib_store='LV2') + +def build(bld): + bundle = 'eg-params.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_EXT) + + # Copy other data files to build bundle (build/eg-params.lv2) + for i in ['params.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'params.c', + name = 'params', + target = 'lv2/%s/params' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2') diff --git a/plugins/eg04-sampler.lv2/README.txt b/plugins/eg-sampler.lv2/README.txt index 4eed9e6..8d136fa 100644 --- a/plugins/eg04-sampler.lv2/README.txt +++ b/plugins/eg-sampler.lv2/README.txt @@ -11,3 +11,4 @@ This plugin illustrates: - 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 +- Network-transparent waveform display with incremental peak transmission diff --git a/plugins/eg-sampler.lv2/atom_sink.h b/plugins/eg-sampler.lv2/atom_sink.h new file mode 100644 index 0000000..b84ca55 --- /dev/null +++ b/plugins/eg-sampler.lv2/atom_sink.h @@ -0,0 +1,42 @@ +/* + Copyright 2016 David Robillard <d@drobilla.net> + + 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 "lv2/atom/forge.h" + +/** + A forge sink that writes to an atom buffer. + + It is assumed that the handle points to an LV2_Atom large enough to store + the forge output. The forged result is in the body of the buffer atom. +*/ +static LV2_Atom_Forge_Ref +atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void* buf, uint32_t size) +{ + LV2_Atom* atom = (LV2_Atom*)handle; + const uint32_t offset = lv2_atom_total_size(atom); + memcpy((char*)atom + offset, buf, size); + atom->size += size; + return offset; +} + +/** + Dereference counterpart to atom_sink(). +*/ +static LV2_Atom* +atom_sink_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) +{ + return (LV2_Atom*)((char*)handle + ref); +} diff --git a/plugins/eg04-sampler.lv2/click.wav b/plugins/eg-sampler.lv2/click.wav Binary files differindex 520a18c..520a18c 100644 --- a/plugins/eg04-sampler.lv2/click.wav +++ b/plugins/eg-sampler.lv2/click.wav diff --git a/plugins/eg04-sampler.lv2/manifest.ttl.in b/plugins/eg-sampler.lv2/manifest.ttl.in index 8a01428..8a01428 100644 --- a/plugins/eg04-sampler.lv2/manifest.ttl.in +++ b/plugins/eg-sampler.lv2/manifest.ttl.in diff --git a/plugins/eg-sampler.lv2/peaks.h b/plugins/eg-sampler.lv2/peaks.h new file mode 100644 index 0000000..45d3465 --- /dev/null +++ b/plugins/eg-sampler.lv2/peaks.h @@ -0,0 +1,271 @@ +/* + LV2 audio peaks utilities + Copyright 2016 David Robillard <d@drobilla.net> + + 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. +*/ + +/** + This file defines utilities for sending and receiving audio peaks for + waveform display. The functionality is divided into two objects: + PeaksSender, for sending peaks updates from the plugin, and PeaksReceiver, + for receiving such updates and caching the peaks. + + This allows peaks for a waveform of any size at any resolution to be + requested, with reasonably sized incremental updates sent over plugin ports. +*/ + +#ifndef PEAKS_H_INCLUDED +#define PEAKS_H_INCLUDED + +#include "lv2/atom/forge.h" + +#include <math.h> +#include <stdlib.h> + +#define PEAKS_URI "http://lv2plug.in/ns/peaks#" +#define PEAKS__PeakUpdate PEAKS_URI "PeakUpdate" +#define PEAKS__magnitudes PEAKS_URI "magnitudes" +#define PEAKS__offset PEAKS_URI "offset" +#define PEAKS__total PEAKS_URI "total" + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +typedef struct { + LV2_URID atom_Float; + LV2_URID atom_Int; + LV2_URID atom_Vector; + LV2_URID peaks_PeakUpdate; + LV2_URID peaks_magnitudes; + LV2_URID peaks_offset; + LV2_URID peaks_total; +} PeaksURIs; + +typedef struct { + PeaksURIs uris; ///< URIDs used in protocol + const float* samples; ///< Sample data + uint32_t n_samples; ///< Total number of samples + uint32_t n_peaks; ///< Total number of peaks + uint32_t current_offset; ///< Current peak offset + bool sending; ///< True iff currently sending +} PeaksSender; + +typedef struct { + PeaksURIs uris; ///< URIDs used in protocol + float* peaks; ///< Received peaks, or zeroes + uint32_t n_peaks; ///< Total number of peaks +} PeaksReceiver; + +/** + Map URIs used in the peaks protocol. +*/ +static inline void +peaks_map_uris(PeaksURIs* uris, LV2_URID_Map* map) +{ + uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); + uris->atom_Int = map->map(map->handle, LV2_ATOM__Int); + uris->atom_Vector = map->map(map->handle, LV2_ATOM__Vector); + uris->peaks_PeakUpdate = map->map(map->handle, PEAKS__PeakUpdate); + uris->peaks_magnitudes = map->map(map->handle, PEAKS__magnitudes); + uris->peaks_offset = map->map(map->handle, PEAKS__offset); + uris->peaks_total = map->map(map->handle, PEAKS__total); +} + +/** + Initialise peaks sender. The new sender is inactive and will do nothing + when `peaks_sender_send()` is called, until a transmission is started with + `peaks_sender_start()`. +*/ +static inline PeaksSender* +peaks_sender_init(PeaksSender* sender, LV2_URID_Map* map) +{ + memset(sender, 0, sizeof(*sender)); + peaks_map_uris(&sender->uris, map); + return sender; +} + +/** + Prepare to start a new peaks transmission. After this is called, the peaks + can be sent with successive calls to `peaks_sender_send()`. +*/ +static inline void +peaks_sender_start(PeaksSender* sender, + const float* samples, + uint32_t n_samples, + uint32_t n_peaks) +{ + sender->samples = samples; + sender->n_samples = n_samples; + sender->n_peaks = n_peaks; + sender->current_offset = 0; + sender->sending = true; +} + +/** + Forge a message which sends a range of peaks. Writes a peaks:PeakUpdate + object to `forge`, like: + + [source,turtle] + ---- + [] + a peaks:PeakUpdate ; + peaks:offset 256 ; + peaks:total 1024 ; + peaks:magnitudes [ 0.2f, 0.3f, ... ] . + ---- +*/ +static inline bool +peaks_sender_send(PeaksSender* sender, + LV2_Atom_Forge* forge, + uint32_t n_frames, + uint32_t offset) +{ + const PeaksURIs* uris = &sender->uris; + if (!sender->sending || sender->current_offset >= sender->n_peaks) { + return sender->sending = false; + } + + // Start PeakUpdate object + lv2_atom_forge_frame_time(forge, offset); + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(forge, &frame, 0, uris->peaks_PeakUpdate); + + // eg:offset = OFFSET + lv2_atom_forge_key(forge, uris->peaks_offset); + lv2_atom_forge_int(forge, sender->current_offset); + + // eg:total = TOTAL + lv2_atom_forge_key(forge, uris->peaks_total); + lv2_atom_forge_int(forge, sender->n_peaks); + + // eg:magnitudes = Vector<Float>(PEAK, PEAK, ...) + lv2_atom_forge_key(forge, uris->peaks_magnitudes); + LV2_Atom_Forge_Frame vec_frame; + lv2_atom_forge_vector_head( + forge, &vec_frame, sizeof(float), uris->atom_Float); + + // Calculate how many peaks to send this update + const int chunk_size = MAX(1, sender->n_samples / sender->n_peaks); + const uint32_t space = forge->size - forge->offset; + const uint32_t remaining = sender->n_peaks - sender->current_offset; + const int n_update = MIN(remaining, + MIN(n_frames / 4, space / sizeof(float))); + + // Calculate peak (maximum magnitude) for each chunk + for (int i = 0; i < n_update; ++i) { + const int start = (sender->current_offset + i) * chunk_size; + float peak = 0.0f; + for (int j = 0; j < chunk_size; ++j) { + peak = fmaxf(peak, fabsf(sender->samples[start + j])); + } + lv2_atom_forge_float(forge, peak); + } + + // Finish message + lv2_atom_forge_pop(forge, &vec_frame); + lv2_atom_forge_pop(forge, &frame); + + sender->current_offset += n_update; + return true; +} + +/** + Initialise a peaks receiver. The receiver stores an array of all peaks, + which is updated incrementally with peaks_receiver_receive(). +*/ +static inline PeaksReceiver* +peaks_receiver_init(PeaksReceiver* receiver, LV2_URID_Map* map) +{ + memset(receiver, 0, sizeof(*receiver)); + peaks_map_uris(&receiver->uris, map); + return receiver; +} + +/** + Clear stored peaks and free all memory. This should be called when the + peaks are to be updated with a different audio source. +*/ +static inline void +peaks_receiver_clear(PeaksReceiver* receiver) +{ + free(receiver->peaks); + receiver->peaks = NULL; + receiver->n_peaks = 0; +} + +/** + Handle PeakUpdate message. + + The stored peaks array is updated with the slice of peaks in `update`, + resizing if necessary while preserving contents. + + Returns 0 if peaks have been updated, negative on error. +*/ +static inline int +peaks_receiver_receive(PeaksReceiver* receiver, const LV2_Atom_Object* update) +{ + const PeaksURIs* uris = &receiver->uris; + + // Get properties of interest from update + const LV2_Atom_Int* offset = NULL; + const LV2_Atom_Int* total = NULL; + const LV2_Atom_Vector* peaks = NULL; + lv2_atom_object_get_typed(update, + uris->peaks_offset, &offset, uris->atom_Int, + uris->peaks_total, &total, uris->atom_Int, + uris->peaks_magnitudes, &peaks, uris->atom_Vector, + 0); + + if (!offset || !total || !peaks || + peaks->body.child_type != uris->atom_Float) { + return -1; // Invalid update + } + + const uint32_t n = (uint32_t)total->body; + if (receiver->n_peaks != n) { + // Update is for a different total number of peaks, resize + receiver->peaks = (float*)realloc(receiver->peaks, n * sizeof(float)); + if (receiver->n_peaks > 0 && receiver->n_peaks < n) { + /* The peaks array is being expanded. Copy the old peaks, + duplicating each as necessary to fill the new peaks buffer. + This preserves the current peaks so that the peaks array can be + reasonably drawn at any time, but the resolution will increase + as new updates arrive. */ + const int n_per = n / receiver->n_peaks; + for (int i = n - 1; i >= 0; --i) { + receiver->peaks[i] = receiver->peaks[i / n_per]; + } + } else if (receiver->n_peaks > 0) { + /* The peak array is being shrunk. Similar to the above. */ + const int n_per = receiver->n_peaks / n; + for (int i = n - 1; i >= 0; --i) { + receiver->peaks[i] = receiver->peaks[i * n_per]; + } + } + receiver->n_peaks = n; + } + + // Copy vector contents to corresponding range in peaks array + memcpy(receiver->peaks + offset->body, + peaks + 1, + peaks->atom.size - sizeof(LV2_Atom_Vector_Body)); + + return 0; +} + +#endif // PEAKS_H_INCLUDED diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c new file mode 100644 index 0000000..ed49903 --- /dev/null +++ b/plugins/eg-sampler.lv2/sampler.c @@ -0,0 +1,603 @@ +/* + LV2 Sampler Example Plugin + Copyright 2011-2016 David Robillard <d@drobilla.net> + Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org> + Copyright 2011 James Morris <jwm.art.net@gmail.com> + + 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 "atom_sink.h" +#include "peaks.h" +#include "uris.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/state/state.h" +#include "lv2/urid/urid.h" +#include "lv2/worker/worker.h" + +#include <sndfile.h> + +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +enum { + SAMPLER_CONTROL = 0, + SAMPLER_NOTIFY = 1, + SAMPLER_OUT = 2 +}; + +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_Logger logger; + + // Ports + const LV2_Atom_Sequence* control_port; + LV2_Atom_Sequence* notify_port; + float* output_port; + + // Communication utilities + LV2_Atom_Forge_Frame notify_frame; ///< Cached for worker replies + LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread + PeaksSender psend; ///< Audio peaks sender + + // URIs + SamplerURIs uris; + + // Playback state + Sample* sample; + uint32_t frame_offset; + float gain; + sf_count_t frame; + bool play; + bool activated; + bool sample_changed; +} 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(LV2_Log_Logger* logger, const char* path) +{ + lv2_log_trace(logger, "Loading %s\n", path); + + const size_t path_len = strlen(path); + Sample* const sample = (Sample*)calloc(1, sizeof(Sample)); + SF_INFO* const info = &sample->info; + SNDFILE* const sndfile = sf_open(path, SFM_READ, info); + float* data = NULL; + bool error = true; + if (!sndfile || !info->frames) { + lv2_log_error(logger, "Failed to open %s\n", path); + } else if (info->channels != 1) { + lv2_log_error(logger, "%s has %d channels\n", path, info->channels); + } else if (!(data = (float*)malloc(sizeof(float) * info->frames))) { + lv2_log_error(logger, "Failed to allocate memory for sample\n"); + } else { + error = false; + } + + if (error) { + free(sample); + free(data); + sf_close(sndfile); + 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 if (atom->type == self->forge.Object) { + // Handle set message (load sample). + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data; + const char* path = read_set_file(&self->uris, obj); + if (!path) { + lv2_log_error(&self->logger, "Malformed set file request\n"); + return LV2_WORKER_ERR_UNKNOWN; + } + + // Load sample. + Sample* sample = load_sample(&self->logger, path); + if (sample) { + // Send new sample 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; + Sample* old_sample = self->sample; + Sample* new_sample = *(Sample*const*)data; + + // Install the new sample + self->sample = *(Sample*const*)data; + + // Schedule work to free the old sample + SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample }, + old_sample }; + self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); + + // 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, + new_sample->path, + new_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*)calloc(1, sizeof(Sampler)); + if (!self) { + return NULL; + } + + // Get host features + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + LV2_WORKER__schedule, &self->schedule, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + free(self); + return NULL; + } + + // Map URIs and initialise forge + map_sampler_uris(self->map, &self->uris); + lv2_atom_forge_init(&self->forge, self->map); + peaks_sender_init(&self->psend, self->map); + + self->gain = 1.0; + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + Sampler* self = (Sampler*)instance; + free_sample(self, self->sample); + free(self); +} + +static void +activate(LV2_Handle instance) +{ + ((Sampler*)instance)->activated = true; +} + +static void +deactivate(LV2_Handle instance) +{ + ((Sampler*)instance)->activated = false; +} + +/** 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) + +/** + Handle an incoming event in the audio thread. + + This performs any actions triggered by an event, such as the start of sample + playback, a sample change, or responding to requests from the UI. +*/ +static void +handle_event(Sampler* self, LV2_Atom_Event* ev) +{ + SamplerURIs* uris = &self->uris; + PeaksURIs* peaks_uris = &self->psend.uris; + + if (ev->body.type == uris->midi_Event) { + const uint8_t* const msg = (const uint8_t*)LV2_ATOM_BODY_CONST(ev); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + 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) { + // Get the property and value of the set message + const LV2_Atom* property = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + uris->patch_property, &property, + uris->patch_value, &value, + 0); + if (!property) { + lv2_log_error(&self->logger, "Set message with no property\n"); + return; + } else if (property->type != uris->atom_URID) { + lv2_log_error(&self->logger, "Set property is not a URID\n"); + return; + } + + const uint32_t key = ((const LV2_Atom_URID*)property)->body; + if (key == uris->eg_sample) { + // Sample change, send it to the worker. + lv2_log_trace(&self->logger, "Scheduling sample change\n"); + self->schedule->schedule_work(self->schedule->handle, + lv2_atom_total_size(&ev->body), + &ev->body); + } else if (key == uris->param_gain) { + // Gain change + if (value->type == uris->atom_Float) { + self->gain = DB_CO(((LV2_Atom_Float*)value)->body); + } + } + } else if (obj->body.otype == uris->patch_Get && self->sample) { + const LV2_Atom_URID* accept = NULL; + const LV2_Atom_Int* n_peaks = NULL; + lv2_atom_object_get_typed( + obj, + uris->patch_accept, &accept, uris->atom_URID, + peaks_uris->peaks_total, &n_peaks, peaks_uris->atom_Int, 0); + if (accept && accept->body == peaks_uris->peaks_PeakUpdate) { + // Received a request for peaks, prepare for transmission + peaks_sender_start(&self->psend, + self->sample->data, + self->sample->info.frames, + n_peaks->body); + } else { + // Received a get message, emit our state (probably to UI) + lv2_atom_forge_frame_time(&self->forge, self->frame_offset); + write_set_file(&self->forge, &self->uris, + self->sample->path, + self->sample->path_len); + } + } 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); + } + +} + +/** + Output audio for a slice of the current cycle. +*/ +static void +render(Sampler* self, uint32_t start, uint32_t end) +{ + float* output = self->output_port; + + if (self->play && self->sample) { + // Start/continue writing sample to output + for (; start < end; ++start) { + output[start] = self->sample->data[self->frame] * self->gain; + if (++self->frame == self->sample->info.frames) { + self->play = false; // Reached end of sample + break; + } + } + } + + // Write silence to remaining buffer + for (; start < end; ++start) { + output[start] = 0.0f; + } +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Sampler* self = (Sampler*)instance; + + // 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); + + // Send update to UI if sample has changed due to state restore + if (self->sample_changed) { + lv2_atom_forge_frame_time(&self->forge, 0); + write_set_file(&self->forge, &self->uris, + self->sample->path, + self->sample->path_len); + self->sample_changed = false; + } + + // Iterate over incoming events, emitting audio along the way + self->frame_offset = 0; + LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { + // Render output up to the time of this event + render(self, self->frame_offset, ev->time.frames); + + /* Update current frame offset to this event's time. This is stored in + the instance because it is used for sychronous worker event + execution. This allows a sample load event to be executed with + sample accuracy when running in a non-realtime context (such as + exporting a session). */ + self->frame_offset = ev->time.frames; + + // Process this event + handle_event(self, ev); + } + + // Use available space after any emitted events to send peaks + peaks_sender_send(&self->psend, &self->forge, sample_count, self->frame_offset); + + // Render output for the rest of the cycle past the last event + render(self, self->frame_offset, sample_count); +} + +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 = (LV2_State_Map_Path*)lv2_features_data( + features, LV2_STATE__mapPath); + if (!map_path) { + return LV2_STATE_ERR_NO_FEATURE; + } + + // Map absolute sample path to an abstract state path + char* apath = map_path->abstract_path(map_path->handle, self->sample->path); + + // Store eg:sample = abstract path + store(handle, + self->uris.eg_sample, + apath, + strlen(apath) + 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; + + // Get host features + LV2_Worker_Schedule* schedule = NULL; + LV2_State_Map_Path* paths = NULL; + const char* missing = lv2_features_query( + features, + LV2_STATE__mapPath, &paths, true, + LV2_WORKER__schedule, &schedule, false, + NULL); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + return LV2_STATE_ERR_NO_FEATURE; + } + + // Get eg:sample from state + size_t size; + uint32_t type; + uint32_t valflags; + const void* value = retrieve(handle, self->uris.eg_sample, + &size, &type, &valflags); + if (!value) { + lv2_log_error(&self->logger, "Missing eg:sample\n"); + return LV2_STATE_ERR_NO_PROPERTY; + } else if (type != self->uris.atom_Path) { + lv2_log_error(&self->logger, "Non-path eg:sample\n"); + return LV2_STATE_ERR_BAD_TYPE; + } + + // Map abstract state path to absolute path + const char* apath = (const char*)value; + char* path = paths->absolute_path(paths->handle, apath); + + // Replace current sample with the new one + if (!self->activated || !schedule) { + // No scheduling available, load sample immediately + lv2_log_trace(&self->logger, "Synchronous restore\n"); + Sample* sample = load_sample(&self->logger, path); + if (sample) { + free_sample(self, self->sample); + self->sample = sample; + self->sample_changed = true; + } + } else { + // Schedule sample to be loaded by the provided worker + lv2_log_trace(&self->logger, "Scheduling restore\n"); + LV2_Atom_Forge forge; + LV2_Atom* buf = (LV2_Atom*)calloc(1, strlen(path) + 128); + lv2_atom_forge_init(&forge, self->map); + lv2_atom_forge_set_sink(&forge, atom_sink, atom_sink_deref, buf); + write_set_file(&forge, &self->uris, path, strlen(path)); + + const uint32_t msg_size = lv2_atom_pad_size(buf->size); + schedule->schedule_work(self->schedule->handle, msg_size, buf + 1); + free(buf); + } + + free(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, + 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/eg04-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl index f705dd1..f5088f3 100644 --- a/plugins/eg04-sampler.lv2/sampler.ttl +++ b/plugins/eg-sampler.lv2/sampler.ttl @@ -1,12 +1,14 @@ @prefix atom: <http://lv2plug.in/ns/ext/atom#> . @prefix doap: <http://usefulinc.com/ns/doap#> . @prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix param: <http://lv2plug.in/ns/ext/parameters#> . @prefix patch: <http://lv2plug.in/ns/ext/patch#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix state: <http://lv2plug.in/ns/ext/state#> . @prefix ui: <http://lv2plug.in/ns/extensions/ui#> . @prefix urid: <http://lv2plug.in/ns/ext/urid#> . @prefix work: <http://lv2plug.in/ns/ext/worker#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <http://lv2plug.in/plugins/eg-sampler#sample> a lv2:Parameter ; @@ -15,17 +17,19 @@ <http://lv2plug.in/plugins/eg-sampler> a lv2:Plugin ; - doap:name "Example Sampler" ; + doap:name "Exampler" ; doap:license <http://opensource.org/licenses/isc> ; lv2:project <http://lv2plug.in/ns/lv2> ; - lv2:requiredFeature urid:map , + lv2:requiredFeature state:loadDefaultState , + urid:map , work:schedule ; lv2:optionalFeature lv2:hardRTCapable , - state:loadDefaultState ; + state:threadSafeRestore ; lv2:extensionData state:interface , work:interface ; ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ; - patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> ; + patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> , + param:gain ; lv2:port [ a lv2:InputPort , atom:AtomPort ; @@ -53,12 +57,15 @@ lv2:name "Out" ] ; state:state [ - <http://lv2plug.in/plugins/eg-sampler#sample> <click.wav> + <http://lv2plug.in/plugins/eg-sampler#sample> <click.wav> ; + param:gain "1.0"^^xsd:float ] . <http://lv2plug.in/plugins/eg-sampler#ui> a ui:GtkUI ; lv2:requiredFeature urid:map ; + lv2:optionalFeature ui:requestValue ; + lv2:extensionData ui:showInterface ; ui:portNotification [ ui:plugin <http://lv2plug.in/plugins/eg-sampler> ; lv2:symbol "notify" ; diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c new file mode 100644 index 0000000..b5e40db --- /dev/null +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -0,0 +1,465 @@ +/* + LV2 Sampler Example Plugin UI + Copyright 2011-2016 David Robillard <d@drobilla.net> + + 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 "peaks.h" +#include "uris.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/ui/ui.h" +#include "lv2/urid/urid.h" + +#include <cairo.h> +#include <gdk/gdk.h> +#include <glib-object.h> +#include <glib.h> +#include <gobject/gclosure.h> +#include <gtk/gtk.h> + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" + +#define MIN_CANVAS_W 128 +#define MIN_CANVAS_H 80 + +typedef struct { + LV2_Atom_Forge forge; + LV2_URID_Map* map; + LV2UI_Request_Value* request_value; + LV2_Log_Logger logger; + SamplerURIs uris; + PeaksReceiver precv; + + LV2UI_Write_Function write; + LV2UI_Controller controller; + + GtkWidget* box; + GtkWidget* play_button; + GtkWidget* file_button; + GtkWidget* request_file_button; + GtkWidget* button_box; + GtkWidget* canvas; + + uint32_t width; + uint32_t requested_n_peaks; + char* filename; + + uint8_t forge_buf[1024]; + + // Optional show/hide interface + GtkWidget* window; + bool did_init; +} SamplerUI; + +static void +on_file_set(GtkFileChooserButton* widget, void* handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + // Get the filename from the file chooser + char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); + + // Write a set message to the plugin to load new file + lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf)); + LV2_Atom* msg = (LV2_Atom*)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 void +on_request_file(GtkButton* widget, void* handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + ui->request_value->request(ui->request_value->handle, + ui->uris.eg_sample, + 0, + NULL); +} + +static void +on_play_clicked(GtkFileChooserButton* widget, void* handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + struct { + LV2_Atom atom; + uint8_t msg[3]; + } note_on; + + note_on.atom.type = ui->uris.midi_Event; + note_on.atom.size = 3; + note_on.msg[0] = LV2_MIDI_MSG_NOTE_ON; + note_on.msg[1] = 60; + note_on.msg[2] = 60; + ui->write(ui->controller, 0, sizeof(note_on), + ui->uris.atom_eventTransfer, + ¬e_on); +} + +static void +request_peaks(SamplerUI* ui, uint32_t n_peaks) +{ + if (n_peaks == ui->requested_n_peaks) { + return; + } + + lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf)); + + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get); + lv2_atom_forge_key(&ui->forge, ui->uris.patch_accept); + lv2_atom_forge_urid(&ui->forge, ui->precv.uris.peaks_PeakUpdate); + lv2_atom_forge_key(&ui->forge, ui->precv.uris.peaks_total); + lv2_atom_forge_int(&ui->forge, n_peaks); + lv2_atom_forge_pop(&ui->forge, &frame); + + LV2_Atom* msg = lv2_atom_forge_deref(&ui->forge, frame.ref); + ui->write(ui->controller, 0, lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); + + ui->requested_n_peaks = n_peaks; +} + +/** Set Cairo color to a GDK color (to follow Gtk theme). */ +static void +cairo_set_source_gdk(cairo_t* cr, const GdkColor* color) +{ + cairo_set_source_rgb( + cr, color->red / 65535.0, color->green / 65535.0, color->blue / 65535.0); + +} + +static gboolean +on_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data) +{ + SamplerUI* ui = (SamplerUI*)data; + + GtkAllocation size; + gtk_widget_get_allocation(widget, &size); + + ui->width = size.width; + if ((uint32_t)ui->width > 2 * ui->requested_n_peaks) { + request_peaks(ui, 2 * ui->requested_n_peaks); + } + + cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); + + cairo_set_line_width(cr, 1.0); + cairo_translate(cr, 0.5, 0.5); + + const int mid_y = size.height / 2; + + const float* const peaks = ui->precv.peaks; + const int32_t n_peaks = ui->precv.n_peaks; + if (peaks) { + // Draw waveform + const double scale = size.width / ((double)n_peaks - 1.0f); + + // Start at left origin + cairo_move_to(cr, 0, mid_y); + + // Draw line through top peaks + for (int i = 0; i < n_peaks; ++i) { + const float peak = peaks[i]; + cairo_line_to(cr, i * scale, mid_y + (peak / 2.0f) * size.height); + } + + // Continue through bottom peaks + for (int i = n_peaks - 1; i >= 0; --i) { + const float peak = peaks[i]; + cairo_line_to(cr, i * scale, mid_y - (peak / 2.0f) * size.height); + } + + // Close shape + cairo_line_to(cr, 0, mid_y); + + cairo_set_source_gdk(cr, widget->style->mid); + cairo_fill_preserve(cr); + + cairo_set_source_gdk(cr, widget->style->fg); + cairo_stroke(cr); + } + + cairo_destroy(cr); + return TRUE; +} + +static void +destroy_window(SamplerUI* ui) +{ + if (ui->window) { + gtk_container_remove(GTK_CONTAINER(ui->window), ui->box); + gtk_widget_destroy(ui->window); + ui->window = NULL; + } +} + +static gboolean +on_window_closed(GtkWidget* widget, GdkEvent* event, gpointer data) +{ + SamplerUI* ui = (SamplerUI*)data; + + // Remove widget so Gtk doesn't delete it when the window is closed + gtk_container_remove(GTK_CONTAINER(ui->window), ui->box); + ui->window = NULL; + + return FALSE; +} + +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*)calloc(1, sizeof(SamplerUI)); + if (!ui) { + return NULL; + } + + ui->write = write_function; + ui->controller = controller; + ui->width = MIN_CANVAS_W; + *widget = NULL; + ui->window = NULL; + ui->did_init = false; + + // Get host features + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &ui->logger.log , false, + LV2_URID__map, &ui->map, true, + LV2_UI__requestValue, &ui->request_value, false, + NULL); + lv2_log_logger_set_map(&ui->logger, ui->map); + if (missing) { + lv2_log_error(&ui->logger, "Missing feature <%s>\n", missing); + free(ui); + return NULL; + } + + // Map URIs and initialise forge + map_sampler_uris(ui->map, &ui->uris); + lv2_atom_forge_init(&ui->forge, ui->map); + peaks_receiver_init(&ui->precv, ui->map); + + // Construct Gtk UI + ui->box = gtk_vbox_new(FALSE, 4); + ui->play_button = gtk_button_new_with_label("▶"); + ui->canvas = gtk_drawing_area_new(); + ui->button_box = gtk_hbox_new(FALSE, 4); + ui->file_button = gtk_file_chooser_button_new( + "Load Sample", GTK_FILE_CHOOSER_ACTION_OPEN); + ui->request_file_button = gtk_button_new_with_label("Request Sample"); + gtk_widget_set_size_request(ui->canvas, MIN_CANVAS_W, MIN_CANVAS_H); + gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4); + gtk_box_pack_start(GTK_BOX(ui->box), ui->canvas, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(ui->box), ui->button_box, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(ui->button_box), ui->play_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ui->button_box), ui->request_file_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ui->button_box), ui->file_button, TRUE, TRUE, 0); + + g_signal_connect(ui->file_button, "file-set", + G_CALLBACK(on_file_set), ui); + + g_signal_connect(ui->request_file_button, "clicked", + G_CALLBACK(on_request_file), ui); + + g_signal_connect(ui->play_button, "clicked", + G_CALLBACK(on_play_clicked), ui); + + g_signal_connect(G_OBJECT(ui->canvas), "expose_event", + G_CALLBACK(on_canvas_expose), ui); + + // Request state (filename) from plugin + lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf)); + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( + &ui->forge, &frame, 0, ui->uris.patch_Get); + lv2_atom_forge_pop(&ui->forge, &frame); + + ui->write(ui->controller, 0, lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); + + *widget = ui->box; + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + if (ui->window) { + destroy_window(ui); + } + + gtk_widget_destroy(ui->canvas); + gtk_widget_destroy(ui->play_button); + gtk_widget_destroy(ui->file_button); + gtk_widget_destroy(ui->request_file_button); + gtk_widget_destroy(ui->button_box); + gtk_widget_destroy(ui->box); + 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 (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + if (obj->body.otype == ui->uris.patch_Set) { + const char* path = read_set_file(&ui->uris, obj); + if (path && (!ui->filename || strcmp(path, ui->filename))) { + g_free(ui->filename); + ui->filename = g_strdup(path); + gtk_file_chooser_set_filename( + GTK_FILE_CHOOSER(ui->file_button), path); + peaks_receiver_clear(&ui->precv); + ui->requested_n_peaks = 0; + request_peaks(ui, ui->width / 2 * 2); + } else if (!path) { + lv2_log_warning(&ui->logger, "Set message has no path\n"); + } + } else if (obj->body.otype == ui->precv.uris.peaks_PeakUpdate) { + if (!peaks_receiver_receive(&ui->precv, obj)) { + gtk_widget_queue_draw(ui->canvas); + } + } + } else { + lv2_log_error(&ui->logger, "Unknown message type\n"); + } + } else { + lv2_log_warning(&ui->logger, "Unknown port event format\n"); + } +} + +/* Optional non-embedded UI show interface. */ +static int +ui_show(LV2UI_Handle handle) +{ + SamplerUI* ui = (SamplerUI*)handle; + + if (ui->window) { + return 0; + } + + if (!ui->did_init) { + int argc = 0; + gtk_init_check(&argc, NULL); + g_object_ref(ui->box); + ui->did_init = true; + } + + ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_add(GTK_CONTAINER(ui->window), ui->box); + + g_signal_connect(G_OBJECT(ui->window), + "delete-event", + G_CALLBACK(on_window_closed), + handle); + + 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) +{ + SamplerUI* ui = (SamplerUI*)handle; + + if (ui->window) { + destroy_window(ui); + } + + 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_do(false); + } + 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/eg-sampler.lv2/uris.h index e2ec6d0..1609db7 100644 --- a/plugins/eg04-sampler.lv2/uris.h +++ b/plugins/eg-sampler.lv2/uris.h @@ -1,6 +1,6 @@ /* LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard <d@drobilla.net> + Copyright 2011-2016 David Robillard <d@drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -18,27 +18,34 @@ #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" +#include "lv2/log/log.h" +#include "lv2/midi/midi.h" +#include "lv2/parameters/parameters.h" +#include "lv2/patch/patch.h" +#include "lv2/state/state.h" + +#include <stdio.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" +#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" typedef struct { - LV2_URID atom_Blank; + LV2_URID atom_Float; 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 eg_sample; LV2_URID midi_Event; + LV2_URID param_gain; + LV2_URID patch_Get; LV2_URID patch_Set; + LV2_URID patch_accept; LV2_URID patch_property; LV2_URID patch_value; } SamplerURIs; @@ -46,7 +53,7 @@ typedef struct { static inline void map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) { - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); 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); @@ -56,53 +63,54 @@ map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) 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->param_gain = map->map(map->handle, LV2_PARAMETERS__gain); + uris->patch_Get = map->map(map->handle, LV2_PATCH__Get); uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris->patch_accept = map->map(map->handle, LV2_PATCH__accept); uris->patch_property = map->map(map->handle, LV2_PATCH__property); uris->patch_value = map->map(map->handle, LV2_PATCH__value); } -static inline bool -is_object_type(const SamplerURIs* uris, LV2_URID type) -{ - return type == uris->atom_Resource - || type == uris->atom_Blank; -} - /** - * Write a message like the following to @p forge: - * [] - * a patch:Set ; - * patch:property eg:sample ; - * patch:value </home/me/foo.wav> . - */ -static inline LV2_Atom* + Write a message like the following to `forge`: + [source,turtle] + ---- + [] + a patch:Set ; + patch:property eg:sample ; + patch:value </home/me/foo.wav> . + ---- +*/ +static inline LV2_Atom_Forge_Ref write_set_file(LV2_Atom_Forge* forge, const SamplerURIs* uris, const char* filename, - const size_t filename_len) + const uint32_t filename_len) { LV2_Atom_Forge_Frame frame; - LV2_Atom* set = (LV2_Atom*)lv2_atom_forge_blank( - forge, &frame, 1, uris->patch_Set); + LV2_Atom_Forge_Ref set = lv2_atom_forge_object( + forge, &frame, 0, uris->patch_Set); - lv2_atom_forge_property_head(forge, uris->patch_property, 0); + lv2_atom_forge_key(forge, uris->patch_property); lv2_atom_forge_urid(forge, uris->eg_sample); - lv2_atom_forge_property_head(forge, uris->patch_value, 0); + 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 </home/me/foo.wav> . - */ -static inline const LV2_Atom* + Get the file path from `obj` which is a message like: + [source,turtle] + ---- + [] + a patch:Set ; + patch:property eg:sample ; + patch:value </home/me/foo.wav> . + ---- +*/ +static inline const char* read_set_file(const SamplerURIs* uris, const LV2_Atom_Object* obj) { @@ -120,23 +128,23 @@ read_set_file(const SamplerURIs* uris, } else if (property->type != uris->atom_URID) { fprintf(stderr, "Malformed set message has non-URID property.\n"); return NULL; - } else if (((LV2_Atom_URID*)property)->body != uris->eg_sample) { + } 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) { + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, uris->patch_value, &value, 0); + if (!value) { fprintf(stderr, "Malformed set message has no value.\n"); return NULL; - } else if (file_path->type != uris->atom_Path) { + } else if (value->type != uris->atom_Path) { fprintf(stderr, "Set message value is not a Path.\n"); return NULL; } - return file_path; + return (const char*)LV2_ATOM_BODY_CONST(value); } #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..8c640c1 --- /dev/null +++ b/plugins/eg-sampler.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-sampler.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') + conf.check_pkg('sndfile >= 1.0.0', uselib_store='SNDFILE') + conf.check_pkg('gtk+-2.0 >= 2.18.0', + uselib_store='GTK2', + system=True, + mandatory=False) + conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) + +def build(bld): + bundle = 'eg-sampler.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_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 = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'sampler.c', + name = 'sampler', + target = 'lv2/%s/sampler' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = ['M', 'SNDFILE', 'LV2']) + + # Build UI library + if bld.env.HAVE_GTK2: + obj = bld(features = 'c cshlib lv2lib', + source = 'sampler_ui.c', + name = 'sampler_ui', + target = 'lv2/%s/sampler_ui' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = ['GTK2', 'LV2']) diff --git a/plugins/eg05-scope.lv2/README.txt b/plugins/eg-scope.lv2/README.txt index ec3578b..122794c 100644 --- a/plugins/eg05-scope.lv2/README.txt +++ b/plugins/eg-scope.lv2/README.txt @@ -1,17 +1,19 @@ == Simple Oscilloscope == -This plugin displays the wave-form of an incoming audio-signal using a simple +This plugin displays the waveform of an incoming audio signal using a simple GTK+Cairo GUI. This plugin illustrates: -- UI <==> Plugin communication via LV2 atom events -- LV2 Atom vector usage and resize-port extension +- 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 likean oscilloscope, but is not a real oscilloscope implementation: +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. diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg-scope.lv2/examploscope.c index 13281ab..a4f5f3f 100644 --- a/plugins/eg05-scope.lv2/examploscope.c +++ b/plugins/eg-scope.lv2/examploscope.c @@ -1,4 +1,5 @@ /* + Copyright 2016 David Robillard <d@drobilla.net> Copyright 2013 Robin Gareus <robin@gareus.org> Permission to use, copy, modify, and/or distribute this software for any @@ -14,18 +15,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "./uris.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/state/state.h" +#include "lv2/urid/urid.h" + +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> -#include <stdint.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/state/state.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" +#include <string.h> -#include "./uris.h" +/** + ==== Private Plugin Instance Structure ==== -/** 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]; @@ -40,34 +55,30 @@ typedef struct { 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; - /* The state of the UI is stored here, so that the GUI can be displayed and - closed without losing the current settings. It is communicated to the - UI using atom messages. - */ + // UI state bool ui_active; bool send_settings_to_ui; float ui_amp; uint32_t ui_spp; } EgScope; -/** Port indices. */ +/** ==== Port Indices ==== */ typedef enum { - SCO_CONTROL = 0, - SCO_NOTIFY = 1, - SCO_INPUT0 = 2, - SCO_OUTPUT0 = 3, - SCO_INPUT1 = 4, - SCO_OUTPUT1 = 5, + 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; -/** Create plugin instance. */ +/** ==== Instantiate Method ==== */ static LV2_Handle instantiate(const LV2_Descriptor* descriptor, double rate, @@ -84,16 +95,14 @@ instantiate(const LV2_Descriptor* descriptor, } // 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"); + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); free(self); return NULL; } @@ -120,12 +129,11 @@ instantiate(const LV2_Descriptor* descriptor, // 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 a port to a buffer. */ +/** ==== Connect Port Method ==== */ static void connect_port(LV2_Handle handle, uint32_t port, @@ -156,41 +164,49 @@ connect_port(LV2_Handle handle, } /** - Forge vector of raw data. - - @param forge Forge to use. - @param uris Mapped URI identifiers. - @param channel Channel ID to transmit. - @param n_samples Number of audio samples to transmit. - @param data Actual audio data. + ==== 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,turtle] + -------- + [] + 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, - void* data) + const float* data) { LV2_Atom_Forge_Frame frame; - // Forge container object of type 'rawaudio' + // Forge container object of type 'RawAudio' lv2_atom_forge_frame_time(forge, 0); - lv2_atom_forge_blank(forge, &frame, 1, uris->RawAudio); + lv2_atom_forge_object(forge, &frame, 0, uris->RawAudio); - // Add integer attribute 'channelid' - lv2_atom_forge_property_head(forge, uris->channelID, 0); + // Add integer 'channelID' property + lv2_atom_forge_key(forge, uris->channelID); lv2_atom_forge_int(forge, channel); - // Add vector of floats raw 'audiodata' - lv2_atom_forge_property_head(forge, uris->audioData, 0); + // 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 atom-object + // Close off object lv2_atom_forge_pop(forge, &frame); } -/** Process a block of audio */ +/** ==== Run Method ==== */ static void run(LV2_Handle handle, uint32_t n_samples) { @@ -228,27 +244,28 @@ run(LV2_Handle handle, uint32_t n_samples) // Forge container object of type 'ui_state' LV2_Atom_Forge_Frame frame; lv2_atom_forge_frame_time(&self->forge, 0); - lv2_atom_forge_blank(&self->forge, &frame, 1, self->uris.ui_State); + lv2_atom_forge_object(&self->forge, &frame, 0, self->uris.ui_State); // Add UI state as properties - lv2_atom_forge_property_head(&self->forge, self->uris.ui_spp, 0); + lv2_atom_forge_key(&self->forge, self->uris.ui_spp); lv2_atom_forge_int(&self->forge, self->ui_spp); - lv2_atom_forge_property_head(&self->forge, self->uris.ui_amp, 0); + lv2_atom_forge_key(&self->forge, self->uris.ui_amp); lv2_atom_forge_float(&self->forge, self->ui_amp); - lv2_atom_forge_property_head(&self->forge, self->uris.param_sampleRate, 0); + 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) { - LV2_Atom_Event* ev = lv2_atom_sequence_begin(&(self->control)->body); + 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 = (LV2_Atom_Object*)&ev->body; + 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 == self->uris.ui_On) { // If the object is a ui-on, the UI was activated self->ui_active = true; @@ -264,10 +281,10 @@ run(LV2_Handle handle, uint32_t n_samples) self->uris.ui_amp, &, 0); if (spp) { - self->ui_spp = ((LV2_Atom_Int*)spp)->body; + self->ui_spp = ((const LV2_Atom_Int*)spp)->body; } if (amp) { - self->ui_amp = ((LV2_Atom_Float*)amp)->body; + self->ui_amp = ((const LV2_Atom_Float*)amp)->body; } } } @@ -297,6 +314,16 @@ 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, @@ -309,11 +336,6 @@ state_save(LV2_Handle instance, return LV2_STATE_SUCCESS; } - /* Store state values. 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. */ - store(handle, self->uris.ui_spp, (void*)&self->ui_spp, sizeof(uint32_t), self->uris.atom_Int, @@ -343,14 +365,14 @@ state_restore(LV2_Handle instance, 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 = *((uint32_t*)spp); + 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 = *((float*)amp); + self->ui_amp = *((const float*)amp); self->send_settings_to_ui = true; } @@ -367,6 +389,7 @@ extension_data(const char* uri) return NULL; } +/** ==== Plugin Descriptors ==== */ static const LV2_Descriptor descriptor_mono = { SCO_URI "#Mono", instantiate, diff --git a/plugins/eg05-scope.lv2/examploscope.ttl.in b/plugins/eg-scope.lv2/examploscope.ttl.in index 0b76962..0b76962 100644 --- a/plugins/eg05-scope.lv2/examploscope.ttl.in +++ b/plugins/eg-scope.lv2/examploscope.ttl.in diff --git a/plugins/eg05-scope.lv2/examploscope_ui.c b/plugins/eg-scope.lv2/examploscope_ui.c index ffbe573..ce0000c 100644 --- a/plugins/eg05-scope.lv2/examploscope_ui.c +++ b/plugins/eg-scope.lv2/examploscope_ui.c @@ -14,14 +14,28 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <stdio.h> -#include <stdlib.h> +#include "./uris.h" + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/ui/ui.h" +#include "lv2/urid/urid.h" #include <cairo.h> +#include <gdk/gdk.h> +#include <glib-object.h> +#include <glib.h> +#include <gobject/gclosure.h> #include <gtk/gtk.h> -#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" -#include "./uris.h" +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> // Drawing area size #define DAWIDTH (640) @@ -71,6 +85,7 @@ typedef struct { uint32_t n_channels; bool paused; float rate; + bool updating; } EgScopeUI; @@ -85,18 +100,20 @@ send_ui_state(LV2UI_Handle handle) uint8_t obj_buf[1024]; lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - // Event body is a ui_state object + // Start a ui:State object LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_blank( - &ui->forge, &frame, 1, ui->uris.ui_State); + 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_property_head(&ui->forge, ui->uris.ui_spp, 0); + lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp); lv2_atom_forge_int(&ui->forge, ui->stride); + // msg[amplitude] = float - lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_amp, 0); + lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp); lv2_atom_forge_float(&ui->forge, gain); - // Close off forged data + + // Finish ui:State object lv2_atom_forge_pop(&ui->forge, &frame); // Send message to plugin port '0' @@ -118,8 +135,8 @@ send_ui_disable(LV2UI_Handle handle) 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_blank( - &ui->forge, &frame, 1, ui->uris.ui_Off); + 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, @@ -142,8 +159,8 @@ send_ui_enable(LV2UI_Handle handle) 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_blank( - &ui->forge, &frame, 1, ui->uris.ui_On); + 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, @@ -156,7 +173,11 @@ send_ui_enable(LV2UI_Handle handle) static gboolean on_cfg_changed(GtkWidget* widget, gpointer data) { - send_ui_state(data); + EgScopeUI* ui = (EgScopeUI*)data; + if (!ui->updating) { + // Only send UI state if the change is from user interaction + send_ui_state(data); + } return TRUE; } @@ -294,17 +315,17 @@ on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data) /** 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 @@ -412,7 +433,7 @@ instantiate(const LV2UI_Descriptor* descriptor, LV2UI_Widget* widget, const LV2_Feature* const* features) { - EgScopeUI* ui = (EgScopeUI*)malloc(sizeof(EgScopeUI)); + EgScopeUI* ui = (EgScopeUI*)calloc(1, sizeof(EgScopeUI)); if (!ui) { fprintf(stderr, "EgScope.lv2 UI: out of memory\n"); @@ -527,10 +548,10 @@ cleanup(LV2UI_Handle handle) } static int -recv_raw_audio(EgScopeUI* ui, LV2_Atom_Object* obj) +recv_raw_audio(EgScopeUI* ui, const LV2_Atom_Object* obj) { - LV2_Atom* chan_val = NULL; - LV2_Atom* data_val = NULL; + 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, @@ -546,18 +567,18 @@ recv_raw_audio(EgScopeUI* ui, LV2_Atom_Object* obj) } // Get the values we need from the body of the property value atoms - const int32_t chn = ((LV2_Atom_Int*)chan_val)->body; - LV2_Atom_Vector* vec = (LV2_Atom_Vector*)data_val; + 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)) + 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 = (float*)(&vec->body + 1); + const float* data = (const float*)(&vec->body + 1); // Update display update_scope(ui, chn, n_elem, data); @@ -565,11 +586,11 @@ recv_raw_audio(EgScopeUI* ui, LV2_Atom_Object* obj) } static int -recv_ui_state(EgScopeUI* ui, LV2_Atom_Object* obj) +recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj) { - LV2_Atom* spp_val = NULL; - LV2_Atom* amp_val = NULL; - LV2_Atom* rate_val = NULL; + 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, @@ -585,15 +606,17 @@ recv_ui_state(EgScopeUI* ui, LV2_Atom_Object* obj) 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 = ((LV2_Atom_Int*)spp_val)->body; - const float amp = ((LV2_Atom_Float*)amp_val)->body; - const float rate = ((LV2_Atom_Float*)rate_val)->body; + 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 + // Disable transmission and update UI + ui->updating = true; 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->updating = false; ui->rate = rate; return 0; @@ -601,7 +624,7 @@ recv_ui_state(EgScopeUI* ui, LV2_Atom_Object* obj) /** 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 @@ -614,16 +637,16 @@ port_event(LV2UI_Handle handle, uint32_t format, const void* buffer) { - EgScopeUI* ui = (EgScopeUI*)handle; - LV2_Atom* atom = (LV2_Atom*)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) { - LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; + lv2_atom_forge_is_object_type(&ui->forge, atom->type)) { + 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) { diff --git a/plugins/eg05-scope.lv2/manifest.ttl.in b/plugins/eg-scope.lv2/manifest.ttl.in index 028a673..a64aff1 100644 --- a/plugins/eg05-scope.lv2/manifest.ttl.in +++ b/plugins/eg-scope.lv2/manifest.ttl.in @@ -2,16 +2,19 @@ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +# ==== Mono plugin variant ==== <http://lv2plug.in/plugins/eg-scope#Mono> a lv2:Plugin ; lv2:binary <examploscope@LIB_EXT@> ; rdfs:seeAlso <examploscope.ttl> . +# ==== Stereo plugin variant ==== <http://lv2plug.in/plugins/eg-scope#Stereo> a lv2:Plugin ; lv2:binary <examploscope@LIB_EXT@> ; rdfs:seeAlso <examploscope.ttl> . +# ==== Gtk 2.0 UI ==== <http://lv2plug.in/plugins/eg-scope#ui> a ui:GtkUI ; ui:binary <examploscope_ui@LIB_EXT@> ; diff --git a/plugins/eg05-scope.lv2/uris.h b/plugins/eg-scope.lv2/uris.h index bd57551..2ebaf4e 100644 --- a/plugins/eg05-scope.lv2/uris.h +++ b/plugins/eg-scope.lv2/uris.h @@ -17,16 +17,15 @@ #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" +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/parameters/parameters.h" +#include "lv2/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; @@ -50,7 +49,6 @@ typedef struct { 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); diff --git a/plugins/eg-scope.lv2/wscript b/plugins/eg-scope.lv2/wscript new file mode 100644 index 0000000..4333502 --- /dev/null +++ b/plugins/eg-scope.lv2/wscript @@ -0,0 +1,56 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') + conf.check_pkg('cairo >= 1.8.10', uselib_store='CAIRO') + conf.check_pkg('gtk+-2.0 >= 2.18.0', + uselib_store='GTK2', + system=True, + mandatory=False) + +def build(bld): + bundle = 'eg-scope.lv2' + + # Build manifest.ttl by substitution (for portable lib extension) + for i in ['manifest.ttl', 'examploscope.ttl']: + bld(features = 'subst', + source = i + '.in', + target = 'lv2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = bld.env.LV2_LIB_EXT) + + # Build plugin library + obj = bld(features = 'c cshlib lv2lib', + source = 'examploscope.c', + name = 'examploscope', + target = 'lv2/%s/examploscope' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2') + + # Build UI library + if bld.env.HAVE_GTK2: + obj = bld(features = 'c cshlib lv2lib', + source = 'examploscope_ui.c', + name = 'examploscope_ui', + target = 'lv2/%s/examploscope_ui' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'GTK2 CAIRO LV2') diff --git a/plugins/eg01-amp.lv2/manifest.ttl.in b/plugins/eg01-amp.lv2/manifest.ttl.in deleted file mode 100644 index 0da78b0..0000000 --- a/plugins/eg01-amp.lv2/manifest.ttl.in +++ /dev/null @@ -1,101 +0,0 @@ -# LV2 Bundle Manifest -# -# All LV2 plugins are installed as "bundles", a directory with a particular -# format. Inside the bundle, the entry point is a file called "manifest.ttl". -# This file lists what plugins are in this bundle, and which files are (.so, -# .ttl, etc.) are associated with those plugins. -# -# Hosts read bundles' manifest.ttl to discover what plugins (and other -# resources) are available. 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 -# <http://lv2plug.in/ns/lv2core#Plugin> the shorter form `lv2:Plugin` can be -# used. This is just a shorthand for URIs within a file, the prefixes are not -# significant otherwise. - -@prefix lv2: <http://lv2plug.in/ns/lv2core#> . -@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . - -# ==== Data ==== - -<http://lv2plug.in/plugins/eg-amp> - a lv2:Plugin ; - lv2:binary <amp@LIB_EXT@> ; - rdfs:seeAlso <amp.ttl> . - -# 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: -# |================================================================ -# | Subject | Predicate | Object -# | <http://lv2plug.in/plugins/eg-amp> | a | lv2:Plugin -# | <http://lv2plug.in/plugins/eg-amp> | lv2:binary | <amp.so> -# | <http://lv2plug.in/plugins/eg-amp> | rdfs:seeAlso | <amp.ttl> -# |================================================================ -# -# The semicolon is used to continue the previous subject; an equivalent -# but more verbose syntax for the same data is: - -<http://lv2plug.in/plugins/eg-amp> a lv2:Plugin . -<http://lv2plug.in/plugins/eg-amp> lv2:binary <amp.so> . -<http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl> . - -# (Since this data is equivalent, it is safe, if pointless, to list it twice) -# -# Note that 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 -# <http://lv2plug.in/ns/lv2core#binary>. If you encounter a URI in some data -# which you do not understand, try this first. -# -# Note the URI of a plugin does NOT need to be an actual web address, it's just -# a global identifier. It is, however, a good idea to use an actual web -# address if possible, since it can be used to easily access documentation, -# downloads, etc. Note there are compatibility rules for when the URI of a -# plugin must be changed, see the http://lv2plug.in/ns/lv2core[LV2 specification] -# for details. -# -# AUTHORS MUST NOT CREATE URIS AT DOMAINS THEY DO NOT CONTROL WITHOUT -# PERMISSION, AND *ESPECIALLY* MUST NOT CREATE SYNTACTICALLY INVALID URIS, -# E.G. WHERE THE PORTION FOLLOWING "http://" IS NOT AN ACTUAL DOMAIN NAME. If -# you need an example URI, the domain http://example.org/ is reserved for this -# purpose. It is best to use web URIs, e.g. at the domain where plugins are -# hosted for download, even if no actual documents are currently hosted there. -# If this is truly impossible, use a URN, e.g. urn:myplugs:superamp. -# -# A detailed explanation of each statement follows. - -<http://lv2plug.in/plugins/eg-amp> a lv2:Plugin . - -# The `a`, as in ``is a'', is a Turtle shortcut for `rdf:type`. -# `lv2:Plugin` expands to <http://lv2plug.in/ns/lv2core#Plugin> (using the -# `lv2:` prefix above) which is the type of all LV2 plugins. -# This statement means ``<http://lv2plug.in/plugins/eg-amp> is an LV2 plugin''. - -<http://lv2plug.in/plugins/eg-amp> lv2:binary <amp@LIB_EXT@> . - -# This says "this plugin has executable code ("binary") in the file -# named "amp.so", which is located in this bundle. The LV2 specification -# defines that all 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. - -<http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl> . - -# This says ``there is more information about this plugin located 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/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/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/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/sampler.c b/plugins/eg04-sampler.lv2/sampler.c deleted file mode 100644 index 9353d48..0000000 --- a/plugins/eg04-sampler.lv2/sampler.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard <d@drobilla.net> - Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org> - Copyright 2011 James Morris <jwm.art.net@gmail.com> - - 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 <math.h> -#include <stdlib.h> -#include <string.h> -#ifndef __cplusplus -# include <stdbool.h> -#endif - -#include <sndfile.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/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 - size_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 = 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 (is_object_type(uris, ev->body.type)) { - const LV2_Atom_Object* obj = (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_ui.c b/plugins/eg04-sampler.lv2/sampler_ui.c deleted file mode 100644 index 40922ae..0000000 --- a/plugins/eg04-sampler.lv2/sampler_ui.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - LV2 Sampler Example Plugin UI - Copyright 2011-2012 David Robillard <d@drobilla.net> - - 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. -*/ - -/** - @file ui.c Sampler Plugin UI -*/ - -#include <stdlib.h> - -#include <gtk/gtk.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/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; -} 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; - - *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) { - LV2_Atom* atom = (LV2_Atom*)buffer; - if (atom->type == ui->uris.atom_Blank) { - LV2_Atom_Object* obj = (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(file_uri); - gtk_label_set_text(GTK_LABEL(ui->label), uri); - } else { - fprintf(stderr, "Unknown message type.\n"); - } - } else { - fprintf(stderr, "Unknown format.\n"); - } -} - -static const void* -extension_data(const char* uri) -{ - 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/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/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/literasc.py b/plugins/literasc.py index b7b65cd..0bcd8f2 100755 --- a/plugins/literasc.py +++ b/plugins/literasc.py @@ -20,7 +20,7 @@ def format_text(text): def format_code(lang, code): if code.strip() == '': return code - + head = '[source,%s]' % lang sep = '-' * len(head) + '\n' return head + '\n' + sep + code.strip('\n') + '\n' + sep @@ -36,16 +36,30 @@ def format_c_source(filename, file): for line in file: code += line + # Skip initial license comment + if code[0:2] == '/*': + code = code[code.find('*/') + 2:] + for c in code: if prev_c == '/' and c == '*': - output += format_code('c', chunk[0:len(chunk)-1]) - in_comment = True in_comment_start = True n_stars = 1 - chunk = '' + elif in_comment_start: + if c == '*': + n_stars += 1 + else: + if n_stars > 1: + output += format_code('c', chunk[0:len(chunk) - 1]) + chunk = '' + in_comment = True + else: + chunk += '*' + c + in_comment_start = False elif in_comment and prev_c == '*' and c == '/': - if n_stars > 2: - output += format_text(chunk[0:len(chunk)-1]) + if n_stars > 1: + output += format_text(chunk[0:len(chunk) - 1]) + else: + output += format_code('c', '/* ' + chunk[0:len(chunk) - 1] + '*/') in_comment = False in_comment_start = False chunk = '' @@ -73,7 +87,7 @@ def format_ttl_source(filename, file): chunk = line else: if is_comment: - output += format_code('txt', chunk) + output += format_code('turtle', chunk) in_comment = True chunk = line.strip().lstrip('# ') + ' \n' else: @@ -82,7 +96,7 @@ def format_ttl_source(filename, file): if in_comment: return output + format_text(chunk) else: - return output + format_code('txt', chunk) + return output + format_code('turtle', chunk) def gen(out, filenames): for filename in filenames: @@ -91,7 +105,7 @@ def gen(out, filenames): sys.stderr.write('Failed to open file %s\n' % filename) continue - if filename.endswith('.c'): + if filename.endswith('.c') or filename.endswith('.h'): out.write(format_c_source(filename, file)) elif filename.endswith('.ttl') or filename.endswith('.ttl.in'): out.write(format_ttl_source(filename, file)) @@ -101,10 +115,10 @@ def gen(out, filenames): out.write('\n') else: sys.stderr.write("Unknown source format `%s'" % ( - filename[filename.find('.'):])) + filename[filename.find('.'):])) file.close() - + if __name__ == "__main__": if len(sys.argv) < 2: sys.stderr.write('Usage: %s FILENAME...\n' % sys.argv[1]) diff --git a/plugins/wscript b/plugins/wscript index 63d01ae..f5f6571 100644 --- a/plugins/wscript +++ b/plugins/wscript @@ -15,20 +15,31 @@ def bld_book_src(task): filenames += [i.abspath()] literasc.gen(open(task.outputs[0].abspath(), 'w'), filenames) - + 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', + 'eg-params.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) files += bld.path.ant_glob('%s/*.c' % i) + files += bld.path.ant_glob('%s/*.h' % i) + # Compile book sources into book.txt asciidoc source bld(rule = bld_book_src, source = files, target = 'book.txt') - bld(rule = 'asciidoc -b html -o ${TGT} ${SRC}', + # Run asciidoc to generate book.html + stylesdir = bld.path.find_node('../doc/').abspath() + pygments_style = bld.path.find_node('../doc/style.css').abspath() + bld(rule = 'asciidoc -a stylesdir=%s -a source-highlighter=pygments -a pygments-style=%s -b html -o ${TGT} ${SRC}' % ( + stylesdir, pygments_style), source = 'book.txt', target = 'book.html') - |