From b91e1a81db7b45d0460da1c8a134d855e0ff265c Mon Sep 17 00:00:00 2001 From: David Robillard Date: Mon, 11 Feb 2013 00:18:55 +0000 Subject: Order examples in a sensible progression for the book. --- plugins/eg-amp.lv2/README.txt | 21 -- plugins/eg-amp.lv2/amp.c | 225 -------------- plugins/eg-amp.lv2/amp.ttl | 77 ----- plugins/eg-amp.lv2/manifest.ttl.in | 101 ------ plugins/eg-amp.lv2/waf | 1 - plugins/eg-amp.lv2/wscript | 67 ---- plugins/eg-metro.lv2/README.txt | 9 - plugins/eg-metro.lv2/manifest.ttl.in | 7 - plugins/eg-metro.lv2/metro.c | 347 --------------------- plugins/eg-metro.lv2/metro.ttl | 39 --- plugins/eg-metro.lv2/waf | 1 - plugins/eg-metro.lv2/wscript | 64 ---- plugins/eg-midigate.lv2/README.txt | 10 - plugins/eg-midigate.lv2/manifest.ttl.in | 10 - plugins/eg-midigate.lv2/midigate.c | 223 ------------- plugins/eg-midigate.lv2/midigate.ttl | 80 ----- plugins/eg-midigate.lv2/waf | 1 - plugins/eg-midigate.lv2/wscript | 65 ---- plugins/eg-sampler.lv2/README.txt | 1 - plugins/eg-sampler.lv2/click.wav | Bin 644 -> 0 bytes plugins/eg-sampler.lv2/manifest.ttl.in | 13 - plugins/eg-sampler.lv2/sampler.c | 498 ------------------------------ plugins/eg-sampler.lv2/sampler.ttl | 83 ----- plugins/eg-sampler.lv2/sampler_ui.c | 201 ------------ plugins/eg-sampler.lv2/uris.h | 142 --------- plugins/eg-sampler.lv2/waf | 1 - plugins/eg-sampler.lv2/wscript | 80 ----- plugins/eg-synth.lv2/manifest.ttl.in | 9 - plugins/eg-synth.lv2/synth.c | 171 ---------- plugins/eg-synth.lv2/synth.ttl | 44 --- plugins/eg-synth.lv2/waf | 1 - plugins/eg-synth.lv2/wscript | 64 ---- plugins/eg01-amp.lv2/README.txt | 21 ++ plugins/eg01-amp.lv2/amp.c | 225 ++++++++++++++ plugins/eg01-amp.lv2/amp.ttl | 86 ++++++ plugins/eg01-amp.lv2/manifest.ttl.in | 101 ++++++ plugins/eg01-amp.lv2/waf | 1 + plugins/eg01-amp.lv2/wscript | 67 ++++ plugins/eg02-midigate.lv2/README.txt | 10 + plugins/eg02-midigate.lv2/manifest.ttl.in | 10 + plugins/eg02-midigate.lv2/midigate.c | 223 +++++++++++++ plugins/eg02-midigate.lv2/midigate.ttl | 80 +++++ plugins/eg02-midigate.lv2/waf | 1 + plugins/eg02-midigate.lv2/wscript | 65 ++++ plugins/eg03-metro.lv2/README.txt | 9 + plugins/eg03-metro.lv2/manifest.ttl.in | 7 + plugins/eg03-metro.lv2/metro.c | 345 +++++++++++++++++++++ plugins/eg03-metro.lv2/metro.ttl | 39 +++ plugins/eg03-metro.lv2/waf | 1 + plugins/eg03-metro.lv2/wscript | 64 ++++ plugins/eg04-sampler.lv2/README.txt | 1 + plugins/eg04-sampler.lv2/click.wav | Bin 0 -> 644 bytes plugins/eg04-sampler.lv2/manifest.ttl.in | 13 + plugins/eg04-sampler.lv2/sampler.c | 498 ++++++++++++++++++++++++++++++ plugins/eg04-sampler.lv2/sampler.ttl | 83 +++++ plugins/eg04-sampler.lv2/sampler_ui.c | 201 ++++++++++++ plugins/eg04-sampler.lv2/uris.h | 142 +++++++++ plugins/eg04-sampler.lv2/waf | 1 + plugins/eg04-sampler.lv2/wscript | 80 +++++ 59 files changed, 2374 insertions(+), 2656 deletions(-) delete mode 100644 plugins/eg-amp.lv2/README.txt delete mode 100644 plugins/eg-amp.lv2/amp.c delete mode 100644 plugins/eg-amp.lv2/amp.ttl delete mode 100644 plugins/eg-amp.lv2/manifest.ttl.in delete mode 120000 plugins/eg-amp.lv2/waf delete mode 100644 plugins/eg-amp.lv2/wscript delete mode 100644 plugins/eg-metro.lv2/README.txt delete mode 100644 plugins/eg-metro.lv2/manifest.ttl.in delete mode 100644 plugins/eg-metro.lv2/metro.c delete mode 100644 plugins/eg-metro.lv2/metro.ttl delete mode 120000 plugins/eg-metro.lv2/waf delete mode 100644 plugins/eg-metro.lv2/wscript delete mode 100644 plugins/eg-midigate.lv2/README.txt delete mode 100644 plugins/eg-midigate.lv2/manifest.ttl.in delete mode 100644 plugins/eg-midigate.lv2/midigate.c delete mode 100644 plugins/eg-midigate.lv2/midigate.ttl delete mode 120000 plugins/eg-midigate.lv2/waf delete mode 100644 plugins/eg-midigate.lv2/wscript delete mode 100644 plugins/eg-sampler.lv2/README.txt delete mode 100644 plugins/eg-sampler.lv2/click.wav delete mode 100644 plugins/eg-sampler.lv2/manifest.ttl.in delete mode 100644 plugins/eg-sampler.lv2/sampler.c delete mode 100644 plugins/eg-sampler.lv2/sampler.ttl delete mode 100644 plugins/eg-sampler.lv2/sampler_ui.c delete mode 100644 plugins/eg-sampler.lv2/uris.h delete mode 120000 plugins/eg-sampler.lv2/waf delete mode 100644 plugins/eg-sampler.lv2/wscript delete mode 100644 plugins/eg-synth.lv2/manifest.ttl.in delete mode 100644 plugins/eg-synth.lv2/synth.c delete mode 100644 plugins/eg-synth.lv2/synth.ttl delete mode 120000 plugins/eg-synth.lv2/waf delete mode 100644 plugins/eg-synth.lv2/wscript create mode 100644 plugins/eg01-amp.lv2/README.txt create mode 100644 plugins/eg01-amp.lv2/amp.c create mode 100644 plugins/eg01-amp.lv2/amp.ttl create mode 100644 plugins/eg01-amp.lv2/manifest.ttl.in create mode 120000 plugins/eg01-amp.lv2/waf create mode 100644 plugins/eg01-amp.lv2/wscript create mode 100644 plugins/eg02-midigate.lv2/README.txt create mode 100644 plugins/eg02-midigate.lv2/manifest.ttl.in create mode 100644 plugins/eg02-midigate.lv2/midigate.c create mode 100644 plugins/eg02-midigate.lv2/midigate.ttl create mode 120000 plugins/eg02-midigate.lv2/waf create mode 100644 plugins/eg02-midigate.lv2/wscript create mode 100644 plugins/eg03-metro.lv2/README.txt create mode 100644 plugins/eg03-metro.lv2/manifest.ttl.in create mode 100644 plugins/eg03-metro.lv2/metro.c create mode 100644 plugins/eg03-metro.lv2/metro.ttl create mode 120000 plugins/eg03-metro.lv2/waf create mode 100644 plugins/eg03-metro.lv2/wscript create mode 100644 plugins/eg04-sampler.lv2/README.txt create mode 100644 plugins/eg04-sampler.lv2/click.wav create mode 100644 plugins/eg04-sampler.lv2/manifest.ttl.in create mode 100644 plugins/eg04-sampler.lv2/sampler.c create mode 100644 plugins/eg04-sampler.lv2/sampler.ttl create mode 100644 plugins/eg04-sampler.lv2/sampler_ui.c create mode 100644 plugins/eg04-sampler.lv2/uris.h create mode 120000 plugins/eg04-sampler.lv2/waf create mode 100644 plugins/eg04-sampler.lv2/wscript diff --git a/plugins/eg-amp.lv2/README.txt b/plugins/eg-amp.lv2/README.txt deleted file mode 100644 index c2ab37d..0000000 --- a/plugins/eg-amp.lv2/README.txt +++ /dev/null @@ -1,21 +0,0 @@ -== Simple Amplifier == - -This plugin is a simple example of a basic LV2 plugin with no additional features. -It has audio ports which contain an array of `float`, -and control ports which contain 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++) and defines the executable portions of the plugin. -Static data is described separately in human and machine readable files in the 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. -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. - * A standard format allows the re-use of existing vocabularies to describe plugins - * The data inherently integrates with the web, databases, etc. - * Labels and documentation are translatable, and available to hosts for display in user interfaces diff --git a/plugins/eg-amp.lv2/amp.c b/plugins/eg-amp.lv2/amp.c deleted file mode 100644 index 8dd7b4f..0000000 --- a/plugins/eg-amp.lv2/amp.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - Copyright 2006-2011 David Robillard - Copyright 2006 Steve Harris - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** Include standard C headers */ -#include -#include - -/** - LV2 headers are based on the URI of the specification they come from, so a - consistent convention can be used even for unofficial extensions. The URI - of the core LV2 specification is , by - replacing `http:/` with `lv2` any header in the specification bundle can be - included, in this case `lv2.h`. -*/ -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -/** - The URI is the identifier for a plugin, and how the host associates this - implementation in code with its description in data. In this plugin it is - only used once in the code, but defining the plugin URI at the top of the - file is a good convention to follow. If this URI does not match that used - in the data files, the host will fail to load the plugin. -*/ -#define AMP_URI "http://lv2plug.in/plugins/eg-amp" - -/** - In code, ports are referred to by index. An enumeration of port indices - should be defined for readability. -*/ -typedef enum { - AMP_GAIN = 0, - AMP_INPUT = 1, - AMP_OUTPUT = 2 -} PortIndex; - -/** - Every plugin defines a private structure for the plugin instance. All data - associated with a plugin instance is stored here, and is available to - every instance method. In this simple plugin, only port buffers need to be - stored, since there is no additional instance data. */ -typedef struct { - // Port buffers - const float* gain; - const float* input; - float* output; -} Amp; - -/** - The instantiate() function is called by the host to create a new plugin - instance. The host passes the plugin descriptor, sample rate, and bundle - path for plugins that need to load additional resources (e.g. waveforms). - The features parameter contains host-provided features defined in LV2 - extensions, but this simple plugin does not use any. - - This function is in the ``instantiation'' threading class, so no other - methods on this instance will be called concurrently with it. -*/ -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature* const* features) -{ - Amp* amp = (Amp*)malloc(sizeof(Amp)); - - return (LV2_Handle)amp; -} - -/** - The connect_port() method is called by the host to connect a particular port - to a buffer. The plugin must store the data location, but data may not be - accessed except in run(). - - This method is in the ``audio'' threading class, and is called in the same - context as run(). -*/ -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Amp* amp = (Amp*)instance; - - switch ((PortIndex)port) { - case AMP_GAIN: - amp->gain = (const float*)data; - break; - case AMP_INPUT: - amp->input = (const float*)data; - break; - case AMP_OUTPUT: - amp->output = (float*)data; - break; - } -} - -/** - The activate() method is called by the host to initialise and prepare the - plugin instance for running. The plugin must reset all internal state - except for buffer locations set by connect_port(). Since this plugin has - no other internal state, this method does nothing. - - This method is in the ``instantiation'' threading class, so no other - methods on this instance will be called concurrently with it. -*/ -static void -activate(LV2_Handle instance) -{ -} - -/** Define a macro for converting a gain in dB to a coefficient */ -#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) - -/** Process a block of audio (audio thread, must be RT safe). */ -static void -run(LV2_Handle instance, uint32_t n_samples) -{ - const Amp* amp = (const Amp*)instance; - - const float gain = *(amp->gain); - const float* const input = amp->input; - float* const output = amp->output; - - const float coef = DB_CO(gain); - - for (uint32_t pos = 0; pos < n_samples; pos++) { - output[pos] = input[pos] * coef; - } -} - -/** - The deactivate() method is the counterpart to activate() called by the host - after running the plugin. It indicates that the host will not call run() - again until another call to activate() and is mainly useful for more - advanced plugins with ``live'' characteristics such as those with auxiliary - processing threads. As with activate(), this plugin has no use for this - information so this method does nothing. - - This method is in the ``instantiation'' threading class, so no other - methods on this instance will be called concurrently with it. -*/ -static void -deactivate(LV2_Handle instance) -{ -} - -/** - Destroy a plugin instance (counterpart to instantiate()). - - This method is in the ``instantiation'' threading class, so no other - methods on this instance will be called concurrently with it. -*/ -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -/** - The extension_data function returns any extension data supported by the - plugin. Note that this is not an instance method, but a function on the - plugin descriptor. It is usually used by plugins to implement additional - interfaces. This plugin does not have any extension data, so this function - returns NULL. - - This method is in the ``discovery'' threading class, so no other functions - or methods in this plugin library will be called concurrently with it. -*/ -static const void* -extension_data(const char* uri) -{ - return NULL; -} - -/** - Define the LV2_Descriptor for this plugin. It is best to define descriptors - statically to avoid leaking memory and non-portable shared library - constructors and destructors to clean up properly. -*/ -static const LV2_Descriptor descriptor = { - AMP_URI, - instantiate, - connect_port, - activate, - run, - deactivate, - cleanup, - extension_data -}; - -/** - The lv2_descriptor() function is the entry point to the plugin library. The - host will load the library and call this function repeatedly with increasing - indices to find all the plugins defined in the library. The index is not an - indentifier, the URI of the returned descriptor is used to determine the - identify of the plugin. - - This method is in the ``discovery'' threading class, so no other functions - or methods in this plugin library will be called concurrently with it. -*/ -LV2_SYMBOL_EXPORT -const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg-amp.lv2/amp.ttl b/plugins/eg-amp.lv2/amp.ttl deleted file mode 100644 index 25e4320..0000000 --- a/plugins/eg-amp.lv2/amp.ttl +++ /dev/null @@ -1,77 +0,0 @@ -@prefix doap: . -@prefix lv2: . -@prefix rdf: . -@prefix rdfs: . - - - a lv2:Plugin , - lv2:AmplifierPlugin ; -# Plugins are associated with a project, where common information like -# developers, home page, and so on are described. This plugin is part of the -# LV2 project, which has URI , and is described -# elsewhere. Typical plugin collections will describe the project in -# manifest.ttl - lv2:project ; -# Every plugin must have a name, described with the doap:name property. -# Translations to various languages can be added by putting a language tag -# after strings as shown. - doap:name "Simple Amplifier" , - "简单放大器"@ch , - "Einfacher Verstärker"@de , - "Simple Amp"@en-gb , - "Amplificador Simple"@es , - "Amplificateur de Base"@fr , - "Amplificatore Semplice"@it , - "簡単なアンプ"@jp , - "Просто Усилитель"@ru ; - doap:license ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ -# Every port must have at least two types, one that specifies direction -# (lv2:InputPort or lv2:OutputPort), and another to describe the data type. -# This port is a lv2:ControlPort, which means it contains a single float. - a lv2:InputPort , - lv2:ControlPort ; - lv2:index 0 ; - lv2:symbol "gain" ; - lv2:name "Gain" , - "收益"@ch , - "Gewinn"@de , - "Gain"@en-gb , - "Aumento"@es , - "Gain"@fr , - "Guadagno"@it , - "利益"@jp , - "Увеличение"@ru ; -# An lv2:ControlPort should always describe its default value, and usually a -# minimum and maximum value. Defining a range is not strictly required, but -# should be done wherever possible to aid host support, particularly for UIs. - lv2:default 0.0 ; - lv2:minimum -90.0 ; - lv2:maximum 24.0 ; - lv2:scalePoint [ - rdfs:label "+5" ; - rdf:value 5.0 - ] , [ - rdfs:label "0" ; - rdf:value 0.0 - ] , [ - rdfs:label "-5" ; - rdf:value -5.0 - ] , [ - rdfs:label "-10" ; - rdf:value -10.0 - ] - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 1 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg-amp.lv2/manifest.ttl.in b/plugins/eg-amp.lv2/manifest.ttl.in deleted file mode 100644 index 51f4a79..0000000 --- a/plugins/eg-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 often contain many URIs. To make this more readable, prefixes -# can be defined. For example, with the `lv2:` prefix below, instead of -# the shorter form `lv2:Plugin` can be -# used. This is just a shorthand for URIs within a file, the prefixes are not -# significant otherwise. - -@prefix lv2: . -@prefix rdfs: . - -# ==== Data ==== - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - -# The token `@LIB_EXT@` above is replaced by the build system with the -# appropriate extension for the current platform (e.g. .so, .dylib, .dll). -# This file is called called `manifest.ttl.in` rather than `manifest.ttl` -# to indicate that it is not the final file to be installed. -# This is not necessary, but is a good idea for portable plugins. -# For reability, the 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 -# | | a | lv2:Plugin -# | | lv2:binary | -# | | rdfs:seeAlso | -# |================================================================ -# -# The semicolon is used to continue the previous subject; an equivalent -# but more verbose syntax for the same data is: - - a lv2:Plugin . - lv2:binary . - rdfs:seeAlso . - -# (Since this data is equivalent, it is safe, if pointless, to list it twice) -# -# 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 -# . 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 there is currently no documents hosted there. -# If this is truly impossible, use a URN, e.g. urn:myplugs:superamp. -# -# A detailed explanation of each statement follows. - - a lv2:Plugin . - -# The `a` is a Turtle shortcut for rdf:type and more or less means ``is a''. -# `lv2:Plugin` expands to (using the -# `lv2:` prefix above) which is the type of all LV2 plugins. -# This statement means `` is an LV2 plugin''. - - lv2:binary . - -# This says "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. - - rdfs:seeAlso . - -# 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/eg-amp.lv2/waf b/plugins/eg-amp.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-amp.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg-amp.lv2/wscript b/plugins/eg-amp.lv2/wscript deleted file mode 100644 index d4295ff..0000000 --- a/plugins/eg-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/eg-metro.lv2/README.txt b/plugins/eg-metro.lv2/README.txt deleted file mode 100644 index 5e9a84a..0000000 --- a/plugins/eg-metro.lv2/README.txt +++ /dev/null @@ -1,9 +0,0 @@ -== Metronome == - -This plugin demonstrates tempo synchronisation by clicking on every beat. The -host sends this information to the plugin as events, so an event with new time -and tempo information will be received whenever there is a change. - -Time is assumed to continue rolling at the tempo and speed defined by the last -received tempo event, even across cycles, until a new tempo event is received -or the plugin is deactivated. diff --git a/plugins/eg-metro.lv2/manifest.ttl.in b/plugins/eg-metro.lv2/manifest.ttl.in deleted file mode 100644 index bd93f66..0000000 --- a/plugins/eg-metro.lv2/manifest.ttl.in +++ /dev/null @@ -1,7 +0,0 @@ -@prefix lv2: . -@prefix rdfs: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg-metro.lv2/metro.c b/plugins/eg-metro.lv2/metro.c deleted file mode 100644 index 97821a1..0000000 --- a/plugins/eg-metro.lv2/metro.c +++ /dev/null @@ -1,347 +0,0 @@ -/* - LV2 Metronome Example Plugin - Copyright 2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include -#include -#include -#include -#ifndef __cplusplus -# include -#endif - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/time/time.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#ifndef M_PI -# define M_PI 3.14159265 -#endif - -#define EG_METRO_URI "http://lv2plug.in/plugins/eg-metro" - -typedef struct { - LV2_URID atom_Blank; - LV2_URID atom_Float; - LV2_URID atom_Path; - LV2_URID atom_Resource; - LV2_URID atom_Sequence; - LV2_URID time_Position; - LV2_URID time_barBeat; - LV2_URID time_beatsPerMinute; - LV2_URID time_speed; -} MetroURIs; - -static const double attack_s = 0.005; -static const double decay_s = 0.075; - -enum { - METRO_CONTROL = 0, - METRO_NOTIFY = 1, - METRO_OUT = 2 -}; - -typedef enum { - STATE_ATTACK, - STATE_DECAY, - STATE_OFF -} State; - -typedef struct { - /* Features */ - LV2_URID_Map* map; - - /* URIs */ - MetroURIs uris; - - /* Ports */ - struct { - LV2_Atom_Sequence* control; - LV2_Atom_Sequence* notify; - float* output; - } ports; - - /** The rate, bpm, and speed are the basic information sent by the host. */ - double rate; - float bpm; - float speed; - - /** To keep track of when to play the next click, we need to keep track of - a few pieces of information: */ - - /** - The frames since the start of the last click is stored */ - uint32_t elapsed_len; - - /** - The current play offset in the wave */ - uint32_t wave_offset; - - /** - The current play state (attack, decay, or off) */ - State state; - - /** The wave to play is a simple sine wave generated at instantiation time - based on the sample rate. The length in frames is stored in order to - continuously play the wave in a cycle to avoid discontinuity clicks. */ - float* wave; - uint32_t wave_len; - - /** The continuously playing sine wave is enveloped to provide an actual - metronome tick. This plugin 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. */ - uint32_t attack_len; - uint32_t decay_len; -} Metro; - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Metro* self = (Metro*)instance; - - switch (port) { - case METRO_CONTROL: - self->ports.control = (LV2_Atom_Sequence*)data; - break; - case METRO_NOTIFY: - self->ports.notify = (LV2_Atom_Sequence*)data; - break; - case METRO_OUT: - self->ports.output = (float*)data; - break; - default: - break; - } -} - -static void -activate(LV2_Handle instance) -{ - Metro* self = (Metro*)instance; - - self->elapsed_len = 0; - self->wave_offset = 0; - self->state = STATE_OFF; -} - -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* path, - const LV2_Feature* const* features) -{ - Metro* self = (Metro*)calloc(1, sizeof(Metro)); - if (!self) { - return NULL; - } - - /** Scan host features for URID map */ - LV2_URID_Map* map = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - map = (LV2_URID_Map*)features[i]->data; - } - } - if (!map) { - fprintf(stderr, "Host does not support urid:map.\n"); - free(self); - return NULL; - } - - /** Map URIS */ - MetroURIs* const uris = &self->uris; - self->map = map; - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); - uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); - uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); - uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); - uris->time_Position = map->map(map->handle, LV2_TIME__Position); - uris->time_barBeat = map->map(map->handle, LV2_TIME__barBeat); - uris->time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute); - uris->time_speed = map->map(map->handle, LV2_TIME__speed); - - /** Initialise fields */ - self->rate = rate; - self->bpm = 120.0f; - self->attack_len = attack_s * rate; - self->decay_len = 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 = (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; - } - - return (LV2_Handle)self; -} - -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -static void -play(Metro* self, uint32_t begin, uint32_t end) -{ - float* const output = self->ports.output; - const uint32_t frames_per_beat = 60.0f / self->bpm * self->rate; - - if (self->speed == 0.0f) { - memset(output, 0, (end - begin) * sizeof(float)); - return; - } - - for (uint32_t i = begin; i < end; ++i) { - switch (self->state) { - case STATE_ATTACK: - /** Amplitude increases from 0..1 until attack_len */ - output[i] = self->wave[self->wave_offset] * - self->elapsed_len / (float)self->attack_len; - if (self->elapsed_len >= self->attack_len) { - self->state = STATE_DECAY; - } - break; - case STATE_DECAY: - /** Amplitude decreases from 1..0 until attack_len + decay_len */ - output[i] = 0.0f; - output[i] = self->wave[self->wave_offset] * - (1 - ((self->elapsed_len - self->attack_len) / - (float)self->decay_len)); - if (self->elapsed_len >= self->attack_len + self->decay_len) { - self->state = STATE_OFF; - } - break; - case STATE_OFF: - output[i] = 0.0f; - } - - /** We continuously play the sine wave regardless of envelope */ - self->wave_offset = (self->wave_offset + 1) % self->wave_len; - - /** Update elapsed time and start attack if necessary */ - if (++self->elapsed_len == frames_per_beat) { - self->state = STATE_ATTACK; - self->elapsed_len = 0; - } - } -} - -static void -update_position(Metro* self, const LV2_Atom_Object* obj) -{ - const MetroURIs* uris = &self->uris; - - /** Received new transport position/speed */ - LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; - lv2_atom_object_get(obj, - uris->time_barBeat, &beat, - uris->time_beatsPerMinute, &bpm, - uris->time_speed, &speed, - NULL); - if (bpm && bpm->type == uris->atom_Float) { - /** Tempo changed, update BPM */ - self->bpm = ((LV2_Atom_Float*)bpm)->body; - } - if (speed && speed->type == uris->atom_Float) { - /** Speed changed, e.g. 0 (stop) to 1 (play) */ - self->speed = ((LV2_Atom_Float*)speed)->body; - } - if (beat && beat->type == uris->atom_Float) { - /** Received a beat position, synchronise. - This is a simple hard sync that may cause clicks. - A real plugin would do something more graceful. - */ - const float frames_per_beat = 60.0f / self->bpm * self->rate; - const float bar_beats = ((LV2_Atom_Float*)beat)->body; - const float beat_beats = bar_beats - floorf(bar_beats); - self->elapsed_len = beat_beats * frames_per_beat; - if (self->elapsed_len < self->attack_len) { - self->state = STATE_ATTACK; - } else if (self->elapsed_len < self->attack_len + self->decay_len) { - self->state = STATE_DECAY; - } else { - self->state = STATE_OFF; - } - } -} - -static void -run(LV2_Handle instance, uint32_t sample_count) -{ - Metro* self = (Metro*)instance; - const MetroURIs* uris = &self->uris; - - /** Empty notify output for now */ - LV2_Atom_Sequence* notify = self->ports.notify; - notify->atom.type = self->uris.atom_Sequence; - notify->atom.size = sizeof(LV2_Atom_Sequence_Body); - notify->body.unit = notify->body.pad = 0; - - /** 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); - !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; - if (obj->body.otype == uris->time_Position) { - /** Received position information, update */ - update_position(self, obj); - } - } - - /** Update time for next iteration and move to next event*/ - last_t = ev->time.frames; - } - - /** Play for remainder of cycle */ - play(self, last_t, sample_count); -} - -static const LV2_Descriptor descriptor = { - EG_METRO_URI, - instantiate, - connect_port, - activate, - run, - NULL, // deactivate, - cleanup, - NULL, // extension_data -}; - -LV2_SYMBOL_EXPORT const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg-metro.lv2/metro.ttl b/plugins/eg-metro.lv2/metro.ttl deleted file mode 100644 index a6f297f..0000000 --- a/plugins/eg-metro.lv2/metro.ttl +++ /dev/null @@ -1,39 +0,0 @@ -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix time: . -@prefix urid: . - - - a lv2:Plugin ; - doap:name "Example Metronome" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; -# Since this port supports time:Position, the host knows to deliver time and -# tempo information - atom:supports time:Position ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" ; - ] , [ - a lv2:OutputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports ; - lv2:portProperty lv2:connectionOptional ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify" ; - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" ; - ] . diff --git a/plugins/eg-metro.lv2/waf b/plugins/eg-metro.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-metro.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg-metro.lv2/wscript b/plugins/eg-metro.lv2/wscript deleted file mode 100644 index 40642b6..0000000 --- a/plugins/eg-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/eg-midigate.lv2/README.txt b/plugins/eg-midigate.lv2/README.txt deleted file mode 100644 index 8f4a0f0..0000000 --- a/plugins/eg-midigate.lv2/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -== MIDI Gate == - -This plugin demonstrates: - - * Receiving MIDI input - - * Processing audio based on MIDI events with sample accuracy - - * Supporting MIDI programs which the host can control/automate, or present a - user interface for with human readable labels diff --git a/plugins/eg-midigate.lv2/manifest.ttl.in b/plugins/eg-midigate.lv2/manifest.ttl.in deleted file mode 100644 index d32f1dc..0000000 --- a/plugins/eg-midigate.lv2/manifest.ttl.in +++ /dev/null @@ -1,10 +0,0 @@ -# The manifest.ttl file follows the same template as the previous example. - -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg-midigate.lv2/midigate.c b/plugins/eg-midigate.lv2/midigate.c deleted file mode 100644 index 3b74bfc..0000000 --- a/plugins/eg-midigate.lv2/midigate.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - Copyright 2013 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include - -#include -#include - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" - -typedef enum { - MIDIGATE_CONTROL = 0, - MIDIGATE_IN = 1, - MIDIGATE_OUT = 2 -} PortIndex; - -typedef struct { - // Port buffers - const LV2_Atom_Sequence* control; - const float* in; - float* out; - - // Features - LV2_URID_Map* map; - - struct { - LV2_URID midi_MidiEvent; - } uris; - - unsigned n_active_notes; - unsigned program; -} Midigate; - -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature* const* features) -{ - /** Scan features array for the URID feature we need. */ - LV2_URID_Map* map = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID__map)) { - map = (LV2_URID_Map*)features[i]->data; - break; - } - } - if (!map) { - /** - No URID feature given. This is a host bug since we require this - feature, but should be handled gracefully anyway. - */ - return NULL; - } - - Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); - self->map = map; - self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); - - return (LV2_Handle)self; -} - -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Midigate* self = (Midigate*)instance; - - switch ((PortIndex)port) { - case MIDIGATE_CONTROL: - self->control = (const LV2_Atom_Sequence*)data; - break; - case MIDIGATE_IN: - self->in = (const float*)data; - break; - case MIDIGATE_OUT: - self->out = (float*)data; - break; - } -} - -static void -activate(LV2_Handle instance) -{ - Midigate* self = (Midigate*)instance; - self->n_active_notes = 0; - self->program = 0; -} - -/** - A function to write a chunk of output, to be called from run(). If the gate - is high, then the input will be passed through for this chunk, otherwise - silence is written. -*/ -static void -write_output(Midigate* self, uint32_t offset, uint32_t len) -{ - const bool active = (self->program == 0) - ? (self->n_active_notes > 0) - : (self->n_active_notes == 0); - if (active) { - memcpy(self->out + offset, self->in + offset, len * sizeof(float)); - } else { - memset(self->out + offset, 0, len * sizeof(float)); - } -} - -/** - This plugin works through the cycle in chunks starting at offset zero. The - +offset+ represents the current time within this this cycle, so - the output from 0 to +offset+ has already been written. - - MIDI events are read in a loop. In each iteration, the number of active - notes (on note on and note off) or the program (on program change) is - updated, then the output is written up until the current event time. Then - +offset+ is updated and the next event is processed. After the loop the - final chunk from the last event to the end of the cycle is emitted. - - This pattern of iterating over input events and writing output along the way - is a common idiom for writing sample accurate output based on event input. - - Note that this simple example simply writes input or zero for each sample - based on the gate. A serious implementation would need to envelope the - transition to avoid aliasing. -*/ -static void -run(LV2_Handle instance, uint32_t sample_count) -{ - Midigate* self = (Midigate*)instance; - uint32_t offset = 0; - - LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) { - if (ev->body.type == self->uris.midi_MidiEvent) { - const uint8_t* const msg = (const uint8_t*)(ev + 1); - switch (lv2_midi_message_type(msg)) { - case LV2_MIDI_MSG_NOTE_ON: - ++self->n_active_notes; - break; - case LV2_MIDI_MSG_NOTE_OFF: - --self->n_active_notes; - break; - case LV2_MIDI_MSG_PGM_CHANGE: - if (msg[1] == 0 || msg[1] == 1) { - self->program = msg[1]; - } - break; - default: break; - } - } - - write_output(self, offset, ev->time.frames - offset); - offset = ev->time.frames; - } - - write_output(self, offset, sample_count - offset); -} - -/** - We have no resources to free on deactivation. - Note that the next call to activate will re-initialise the state, namely - self->n_active_notes, so there is no need to do so here. -*/ -static void -deactivate(LV2_Handle instance) -{} - -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -/** - This plugin also has no extension data to return. -*/ -static const void* -extension_data(const char* uri) -{ - return NULL; -} - -static const LV2_Descriptor descriptor = { - MIDIGATE_URI, - instantiate, - connect_port, - activate, - run, - deactivate, - cleanup, - extension_data -}; - -LV2_SYMBOL_EXPORT -const LV2_Descriptor* -lv2_descriptor(uint32_t index) -{ - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } -} diff --git a/plugins/eg-midigate.lv2/midigate.ttl b/plugins/eg-midigate.lv2/midigate.ttl deleted file mode 100644 index 59ac815..0000000 --- a/plugins/eg-midigate.lv2/midigate.ttl +++ /dev/null @@ -1,80 +0,0 @@ -# The same set of namespace prefixes with two additions for LV2 extensions this -# plugin uses: atom and urid. - -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix midi: . -@prefix rdfs: . -@prefix urid: . - - - a lv2:Plugin ; - doap:name "Example MIDI Gate" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map ; - lv2:optionalFeature lv2:hardRTCapable ; -# Describe program banks so the host can automate and/or present a user -# interface. Describing supported programs (or any other MIDI event) is not -# required, but is a good idea since it allows hosts to make better use of -# plugins. This plugin has a single bank of two programs, which have a -# (mandatory) label, and an (optional) comment to describe their meaning in -# more detail. -# -# Both programs and the bank have an index, which corresponds to the MIDI bank -# and program numbers that will activate them. Since there are other ways to -# change programs (not used here), an index is not strictly required, but must -# be present to support program changes from MIDI. - lv2:bank [ - rdfs:label "Default" ; - lv2:index 0 ; - lv2:program [ - lv2:index 0 ; - rdfs:label "Normal" ; - rdfs:comment "Input is passed through if notes are active." - ] , [ - lv2:index 1 ; - rdfs:label "Inverted" ; - rdfs:comment "Input is passed through if no notes are active." - ] - ] ; -# This plugin has three ports. There is an audio input and output as before, -# as well as a new AtomPort. An AtomPort buffer contains an Atom, which is a -# generic container for any type of data. In this case, we want to receive -# MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is -# a series of events with time stamps. -# -# Events themselves are also generic and can contain any type of data, but in -# this case we are only interested in MIDI events. The (optional) -# +atom:supports+ property describes which event types are supported. Though -# not required, this information should always be given so the host knows what -# types of event it can expect the plugin to understand. -# -# The (optional) +lv2:designation+ of this port is +lv2:control+, which -# indicates that this is the "main" control port where the host should send -# events it expects to configure the plugin, in this case changing the MIDI -# program. This is necessary since it is possible to have several MIDI input -# ports, though typically it is best to have one. - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports midi:MidiEvent ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a lv2:AudioPort , - lv2:InputPort ; - lv2:index 1 ; - lv2:symbol "in" ; - lv2:name "In" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg-midigate.lv2/waf b/plugins/eg-midigate.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-midigate.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg-midigate.lv2/wscript b/plugins/eg-midigate.lv2/wscript deleted file mode 100644 index 44336af..0000000 --- a/plugins/eg-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/eg-sampler.lv2/README.txt b/plugins/eg-sampler.lv2/README.txt deleted file mode 100644 index c1cac46..0000000 --- a/plugins/eg-sampler.lv2/README.txt +++ /dev/null @@ -1 +0,0 @@ -== Sampler == diff --git a/plugins/eg-sampler.lv2/click.wav b/plugins/eg-sampler.lv2/click.wav deleted file mode 100644 index 520a18c..0000000 Binary files a/plugins/eg-sampler.lv2/click.wav and /dev/null differ diff --git a/plugins/eg-sampler.lv2/manifest.ttl.in b/plugins/eg-sampler.lv2/manifest.ttl.in deleted file mode 100644 index b4fa23e..0000000 --- a/plugins/eg-sampler.lv2/manifest.ttl.in +++ /dev/null @@ -1,13 +0,0 @@ -@prefix lv2: . -@prefix rdfs: . -@prefix ui: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . - - - a ui:GtkUI ; - ui:binary ; - rdfs:seeAlso . diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c deleted file mode 100644 index 5bb4e54..0000000 --- a/plugins/eg-sampler.lv2/sampler.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard - Copyright 2011 Gabriel M. Beddingfield - Copyright 2011 James Morris - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file sampler.c Sampler Plugin - - A simple example of an LV2 sampler that dynamically loads a single sample - (based on incoming events) and triggers their playback (based on incoming - MIDI note events). - - This plugin illustrates: - - UI <=> Plugin communication via events - - Use of the worker extension for non-realtime tasks (sample loading) - - Use of the log extension to print log messages via the host - - Saving plugin state via the state extension - - Dynamic plugin control via the same properties saved to state -*/ - -#include -#include -#include -#ifndef __cplusplus -# include -#endif - -#include - -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/log/logger.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/ext/worker/worker.h" -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#include "./uris.h" - -enum { - SAMPLER_CONTROL = 0, - SAMPLER_NOTIFY = 1, - SAMPLER_OUT = 2 -}; - -static const char* default_sample_file = "click.wav"; - -typedef struct { - SF_INFO info; /**< Info about sample from sndfile */ - float* data; /**< Sample data in float */ - char* path; /**< Path of file */ - 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/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl deleted file mode 100644 index e008de0..0000000 --- a/plugins/eg-sampler.lv2/sampler.ttl +++ /dev/null @@ -1,83 +0,0 @@ -# LV2 Sampler Example Plugin -# Copyright 2011-2012 David Robillard -# Copyright 2011 Gabriel M. Beddingfield -# Copyright 2011 James Morris -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -@prefix atom: . -@prefix doap: . -@prefix lv2: . -@prefix patch: . -@prefix rdfs: . -@prefix state: . -@prefix ui: . -@prefix urid: . -@prefix work: . - - - a lv2:Parameter ; - rdfs:label "sample" ; - rdfs:range atom:Path . - - - a lv2:Plugin ; - doap:name "Example Sampler" ; - doap:license ; - lv2:project ; - lv2:requiredFeature urid:map , - work:schedule ; - lv2:optionalFeature lv2:hardRTCapable , - state:loadDefaultState ; - lv2:extensionData state:interface , - work:interface ; - ui:ui ; - patch:writable ; - lv2:port [ - a lv2:InputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports , - patch:Message ; - lv2:designation lv2:control ; - lv2:index 0 ; - lv2:symbol "control" ; - lv2:name "Control" - ] , [ - a lv2:OutputPort , - atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports patch:Message ; - lv2:designation lv2:control ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify" - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 2 ; - lv2:symbol "out" ; - lv2:name "Out" - ] ; - state:state [ - - ] . - - - a ui:GtkUI ; - lv2:requiredFeature urid:map ; - ui:portNotification [ - ui:plugin ; - lv2:symbol "notify" ; - ui:notifyType atom:Blank - ] . diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c deleted file mode 100644 index 40922ae..0000000 --- a/plugins/eg-sampler.lv2/sampler_ui.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - LV2 Sampler Example Plugin UI - Copyright 2011-2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file ui.c Sampler Plugin UI -*/ - -#include - -#include - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" -#include "lv2/lv2plug.in/ns/ext/atom/util.h" -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/urid/urid.h" -#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" - -#include "./uris.h" - -#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" - -typedef struct { - LV2_Atom_Forge forge; - - LV2_URID_Map* map; - SamplerURIs uris; - - LV2UI_Write_Function write; - LV2UI_Controller controller; - - GtkWidget* box; - GtkWidget* button; - GtkWidget* label; -} 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/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h deleted file mode 100644 index e2ec6d0..0000000 --- a/plugins/eg-sampler.lv2/uris.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef SAMPLER_URIS_H -#define SAMPLER_URIS_H - -#include "lv2/lv2plug.in/ns/ext/log/log.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" -#include "lv2/lv2plug.in/ns/ext/state/state.h" - -#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" -#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" -#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample" -#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample" - -typedef struct { - LV2_URID atom_Blank; - LV2_URID atom_Path; - LV2_URID atom_Resource; - LV2_URID atom_Sequence; - LV2_URID atom_URID; - LV2_URID atom_eventTransfer; - LV2_URID eg_applySample; - LV2_URID eg_sample; - LV2_URID eg_freeSample; - LV2_URID midi_Event; - LV2_URID patch_Set; - LV2_URID patch_property; - LV2_URID patch_value; -} SamplerURIs; - -static inline void -map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) -{ - uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); - uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); - uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); - uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); - uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); - uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); - uris->eg_applySample = map->map(map->handle, EG_SAMPLER__applySample); - uris->eg_freeSample = map->map(map->handle, EG_SAMPLER__freeSample); - uris->eg_sample = map->map(map->handle, EG_SAMPLER__sample); - uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); - uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); - uris->patch_property = map->map(map->handle, LV2_PATCH__property); - uris->patch_value = map->map(map->handle, LV2_PATCH__value); -} - -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 . - */ -static inline LV2_Atom* -write_set_file(LV2_Atom_Forge* forge, - const SamplerURIs* uris, - const char* filename, - const size_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_property_head(forge, uris->patch_property, 0); - lv2_atom_forge_urid(forge, uris->eg_sample); - lv2_atom_forge_property_head(forge, uris->patch_value, 0); - lv2_atom_forge_path(forge, filename, filename_len); - - lv2_atom_forge_pop(forge, &frame); - - return set; -} - -/** - * Get the file path from a message like: - * [] - * a patch:Set ; - * patch:property eg:sample ; - * patch:value . - */ -static inline const LV2_Atom* -read_set_file(const SamplerURIs* uris, - const LV2_Atom_Object* obj) -{ - if (obj->body.otype != uris->patch_Set) { - fprintf(stderr, "Ignoring unknown message type %d\n", obj->body.otype); - return NULL; - } - - /* Get property URI. */ - const LV2_Atom* property = NULL; - lv2_atom_object_get(obj, uris->patch_property, &property, 0); - if (!property) { - fprintf(stderr, "Malformed set message has no body.\n"); - return NULL; - } else if (property->type != uris->atom_URID) { - fprintf(stderr, "Malformed set message has non-URID property.\n"); - return NULL; - } else if (((LV2_Atom_URID*)property)->body != uris->eg_sample) { - fprintf(stderr, "Set message for unknown property.\n"); - return NULL; - } - - /* Get value. */ - const LV2_Atom* file_path = NULL; - lv2_atom_object_get(obj, uris->patch_value, &file_path, 0); - if (!file_path) { - fprintf(stderr, "Malformed set message has no value.\n"); - return NULL; - } else if (file_path->type != uris->atom_Path) { - fprintf(stderr, "Set message value is not a Path.\n"); - return NULL; - } - - return file_path; -} - -#endif /* SAMPLER_URIS_H */ diff --git a/plugins/eg-sampler.lv2/waf b/plugins/eg-sampler.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-sampler.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg-sampler.lv2/wscript b/plugins/eg-sampler.lv2/wscript deleted file mode 100644 index 732c904..0000000 --- a/plugins/eg-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/eg-synth.lv2/manifest.ttl.in b/plugins/eg-synth.lv2/manifest.ttl.in deleted file mode 100644 index 24acd71..0000000 --- a/plugins/eg-synth.lv2/manifest.ttl.in +++ /dev/null @@ -1,9 +0,0 @@ -# See manifest.ttl.in in the eg-amp.lv2 example for an explanation of this file - -@prefix lv2: . -@prefix rdfs: . - - - a lv2:Plugin ; - lv2:binary ; - rdfs:seeAlso . diff --git a/plugins/eg-synth.lv2/synth.c b/plugins/eg-synth.lv2/synth.c deleted file mode 100644 index 758989b..0000000 --- a/plugins/eg-synth.lv2/synth.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - Copyright 2012 Harry van Haaren - Copyright 2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file synth.c Implementation of the LV2 Sin Synth example plugin. - - This is a simple LV2 synthesis plugin that demonstrates how to receive MIDI - events and render audio in response to them. -*/ - -#include -#include -#include - -#include "lv2/lv2plug.in/ns/lv2core/lv2.h" - -#define SYNTH_URI "http://lv2plug.in/plugins/eg-synth" - -/** Port indices. */ -typedef enum { - SYNTH_FREQ = 0, - SYNTH_OUTPUT, -} PortIndex; - -/** Plugin instance. */ -typedef struct { - // Sample rate, necessary to generate sin wave in run() - double sample_rate; - - // Current wave phase - float phase; - - // Port buffers - const float* freq; - float* output; -} Synth; - -/** Create a new plugin instance. */ -static LV2_Handle -instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature* const* features) -{ - Synth* self = (Synth*)malloc(sizeof(Synth)); - if (self) { - // Store the sample rate so it is available in run() - self->sample_rate = rate; - } - return (LV2_Handle)self; -} - -/** Connect a port to a buffer (audio thread, must be RT safe). */ -static void -connect_port(LV2_Handle instance, - uint32_t port, - void* data) -{ - Synth* self = (Synth*)instance; - - switch ((PortIndex)port) { - case SYNTH_FREQ: - self->freq = (const float*)data; - break; - case SYNTH_OUTPUT: - self->output = (float*)data; - break; - } -} - -/** Initialise and prepare the plugin instance for running. */ -static void -activate(LV2_Handle instance) -{ - Synth* self = (Synth*)instance; - - // Initialize phase so we start at the beginning of the wave - self->phase = 0.0f; -} - -/** Process a block of audio (audio thread, must be RT safe). */ -static void -run(LV2_Handle instance, uint32_t n_samples) -{ - Synth* self = (Synth*)instance; - - const float PI = 3.1415; - const float volume = 0.3; - const float freq = *(self->freq); - float* const output = self->output; - - float samples_per_cycle = self->sample_rate / freq; - - /* Calculate the phase offset per sample. Phase ranges from 0..1, so - phase_increment is a floating point number such that we get "freq" - number of cycles in "sample_rate" amount of samples. */ - float phase_increment = (1.f / samples_per_cycle); - - for (uint32_t pos = 0; pos < n_samples; pos++) { - /* Calculate the next sample. Phase ranges from 0..1, but sin() - expects its input in radians, so we multiply by 2 PI to convert it. - We also multiply by volume so it's not extremely loud. */ - output[pos] = sin(self->phase * 2 * PI) * volume; - - /* Increment the phase so we generate the next sample */ - self->phase += phase_increment; - if (self->phase > 1.0f) { - self->phase = 0.0f; - } - } -} - -/** Finish running (counterpart to activate()). */ -static void -deactivate(LV2_Handle instance) -{ - /* Nothing to do here in this trivial mostly stateless plugin. */ -} - -/** Destroy a plugin instance (counterpart to instantiate()). */ -static void -cleanup(LV2_Handle instance) -{ - free(instance); -} - -/** Return extension data provided by the plugin. */ -static const void* -extension_data(const char* uri) -{ - return NULL; /* This plugin has no extension data. */ -} - -/** The LV2_Descriptor for this plugin. */ -static const LV2_Descriptor descriptor = { - SYNTH_URI, - instantiate, - connect_port, - activate, - run, - deactivate, - cleanup, - extension_data -}; - -/** Entry point, the host will call this function to access descriptors. */ -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-synth.lv2/synth.ttl b/plugins/eg-synth.lv2/synth.ttl deleted file mode 100644 index fb97d56..0000000 --- a/plugins/eg-synth.lv2/synth.ttl +++ /dev/null @@ -1,44 +0,0 @@ -# LV2 Sinewave synth plugin -# Copyright 2012 Harry van Haaren -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -@prefix doap: . -@prefix foaf: . -@prefix lv2: . -@prefix rdf: . -@prefix rdfs: . - - - a lv2:Plugin , - lv2:InstrumentPlugin ; - doap:name "Example Synthesizer" ; - doap:license ; - lv2:project ; - lv2:optionalFeature lv2:hardRTCapable ; - lv2:port [ - a lv2:InputPort , - lv2:ControlPort ; - lv2:index 0 ; - lv2:symbol "frequency" ; - lv2:name "Frequency" ; - lv2:default 440.0 ; - lv2:minimum 40.0 ; - lv2:maximum 880.0 - ] , [ - a lv2:AudioPort , - lv2:OutputPort ; - lv2:index 1 ; - lv2:symbol "out" ; - lv2:name "Out" - ] . diff --git a/plugins/eg-synth.lv2/waf b/plugins/eg-synth.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-synth.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf \ No newline at end of file diff --git a/plugins/eg-synth.lv2/wscript b/plugins/eg-synth.lv2/wscript deleted file mode 100644 index 2069d03..0000000 --- a/plugins/eg-synth.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-synth.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('Synth 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 = APPNAME - - # 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 ['synth.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 = 'synth.c', - name = 'synth', - target = '%s/synth' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - uselib = 'LV2', - includes = includes) - obj.env.cshlib_PATTERN = module_pat diff --git a/plugins/eg01-amp.lv2/README.txt b/plugins/eg01-amp.lv2/README.txt new file mode 100644 index 0000000..f024a4d --- /dev/null +++ b/plugins/eg01-amp.lv2/README.txt @@ -0,0 +1,21 @@ +== Simple Amplifier == + +This plugin is a simple example of a basic LV2 plugin with no additional features. +It has audio ports which contain an array of `float`, +and a control port which contain 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. +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 diff --git a/plugins/eg01-amp.lv2/amp.c b/plugins/eg01-amp.lv2/amp.c new file mode 100644 index 0000000..8dd7b4f --- /dev/null +++ b/plugins/eg01-amp.lv2/amp.c @@ -0,0 +1,225 @@ +/* + Copyright 2006-2011 David Robillard + Copyright 2006 Steve Harris + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** Include standard C headers */ +#include +#include + +/** + LV2 headers are based on the URI of the specification they come from, so a + consistent convention can be used even for unofficial extensions. The URI + of the core LV2 specification is , by + replacing `http:/` with `lv2` any header in the specification bundle can be + included, in this case `lv2.h`. +*/ +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +/** + The URI is the identifier for a plugin, and how the host associates this + implementation in code with its description in data. In this plugin it is + only used once in the code, but defining the plugin URI at the top of the + file is a good convention to follow. If this URI does not match that used + in the data files, the host will fail to load the plugin. +*/ +#define AMP_URI "http://lv2plug.in/plugins/eg-amp" + +/** + In code, ports are referred to by index. An enumeration of port indices + should be defined for readability. +*/ +typedef enum { + AMP_GAIN = 0, + AMP_INPUT = 1, + AMP_OUTPUT = 2 +} PortIndex; + +/** + Every plugin defines a private structure for the plugin instance. All data + associated with a plugin instance is stored here, and is available to + every instance method. In this simple plugin, only port buffers need to be + stored, since there is no additional instance data. */ +typedef struct { + // Port buffers + const float* gain; + const float* input; + float* output; +} Amp; + +/** + The instantiate() function is called by the host to create a new plugin + instance. The host passes the plugin descriptor, sample rate, and bundle + path for plugins that need to load additional resources (e.g. waveforms). + The features parameter contains host-provided features defined in LV2 + extensions, but this simple plugin does not use any. + + This function is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + Amp* amp = (Amp*)malloc(sizeof(Amp)); + + return (LV2_Handle)amp; +} + +/** + The connect_port() method is called by the host to connect a particular port + to a buffer. The plugin must store the data location, but data may not be + accessed except in run(). + + This method is in the ``audio'' threading class, and is called in the same + context as run(). +*/ +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Amp* amp = (Amp*)instance; + + switch ((PortIndex)port) { + case AMP_GAIN: + amp->gain = (const float*)data; + break; + case AMP_INPUT: + amp->input = (const float*)data; + break; + case AMP_OUTPUT: + amp->output = (float*)data; + break; + } +} + +/** + The activate() method is called by the host to initialise and prepare the + plugin instance for running. The plugin must reset all internal state + except for buffer locations set by connect_port(). Since this plugin has + no other internal state, this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +activate(LV2_Handle instance) +{ +} + +/** Define a macro for converting a gain in dB to a coefficient */ +#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) + +/** Process a block of audio (audio thread, must be RT safe). */ +static void +run(LV2_Handle instance, uint32_t n_samples) +{ + const Amp* amp = (const Amp*)instance; + + const float gain = *(amp->gain); + const float* const input = amp->input; + float* const output = amp->output; + + const float coef = DB_CO(gain); + + for (uint32_t pos = 0; pos < n_samples; pos++) { + output[pos] = input[pos] * coef; + } +} + +/** + The deactivate() method is the counterpart to activate() called by the host + after running the plugin. It indicates that the host will not call run() + again until another call to activate() and is mainly useful for more + advanced plugins with ``live'' characteristics such as those with auxiliary + processing threads. As with activate(), this plugin has no use for this + information so this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +deactivate(LV2_Handle instance) +{ +} + +/** + Destroy a plugin instance (counterpart to instantiate()). + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + The extension_data function returns any extension data supported by the + plugin. Note that this is not an instance method, but a function on the + plugin descriptor. It is usually used by plugins to implement additional + interfaces. This plugin does not have any extension data, so this function + returns NULL. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +/** + Define the LV2_Descriptor for this plugin. It is best to define descriptors + statically to avoid leaking memory and non-portable shared library + constructors and destructors to clean up properly. +*/ +static const LV2_Descriptor descriptor = { + AMP_URI, + instantiate, + connect_port, + activate, + run, + deactivate, + cleanup, + extension_data +}; + +/** + The lv2_descriptor() function is the entry point to the plugin library. The + host will load the library and call this function repeatedly with increasing + indices to find all the plugins defined in the library. The index is not an + indentifier, the URI of the returned descriptor is used to determine the + identify of the plugin. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg01-amp.lv2/amp.ttl b/plugins/eg01-amp.lv2/amp.ttl new file mode 100644 index 0000000..f4a87f2 --- /dev/null +++ b/plugins/eg01-amp.lv2/amp.ttl @@ -0,0 +1,86 @@ +# The full description of the plugin is in this file, which is linked to from +# `manifest.ttl`. This is done so the host only needs to scan the relatively +# small `manifest.ttl` files to quickly discover all plugins. + +@prefix doap: . +@prefix lv2: . +@prefix rdf: . +@prefix rdfs: . + +# First the type of the plugin is described. All plugins must explicitly list +# `lv2:Plugin` as a type. A more specific type should also be given, where +# applicable, so hosts can present a nicer UI for loading plugins. Note that +# this URI is the identifier of the plugin, so if it does not match the one in +# `manifest.ttl`, the host will not discover the plugin data at all. + + a lv2:Plugin , + lv2:AmplifierPlugin ; +# Plugins are associated with a project, where common information like +# developers, home page, and so on are described. This plugin is part of the +# LV2 project, which has URI , and is described +# elsewhere. Typical plugin collections will describe the project in +# manifest.ttl + lv2:project ; +# Every plugin must have a name, described with the doap:name property. +# Translations to various languages can be added by putting a language tag +# after strings as shown. + doap:name "Simple Amplifier" , + "简单放大器"@ch , + "Einfacher Verstärker"@de , + "Simple Amp"@en-gb , + "Amplificador Simple"@es , + "Amplificateur de Base"@fr , + "Amplificatore Semplice"@it , + "簡単なアンプ"@jp , + "Просто Усилитель"@ru ; + doap:license ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ +# Every port must have at least two types, one that specifies direction +# (lv2:InputPort or lv2:OutputPort), and another to describe the data type. +# This port is a lv2:ControlPort, which means it contains a single float. + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "gain" ; + lv2:name "Gain" , + "收益"@ch , + "Gewinn"@de , + "Gain"@en-gb , + "Aumento"@es , + "Gain"@fr , + "Guadagno"@it , + "利益"@jp , + "Увеличение"@ru ; +# An lv2:ControlPort should always describe its default value, and usually a +# minimum and maximum value. Defining a range is not strictly required, but +# should be done wherever possible to aid host support, particularly for UIs. + lv2:default 0.0 ; + lv2:minimum -90.0 ; + lv2:maximum 24.0 ; + lv2:scalePoint [ + rdfs:label "+5" ; + rdf:value 5.0 + ] , [ + rdfs:label "0" ; + rdf:value 0.0 + ] , [ + rdfs:label "-5" ; + rdf:value -5.0 + ] , [ + rdfs:label "-10" ; + rdf:value -10.0 + ] + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 1 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . diff --git a/plugins/eg01-amp.lv2/manifest.ttl.in b/plugins/eg01-amp.lv2/manifest.ttl.in new file mode 100644 index 0000000..0da78b0 --- /dev/null +++ b/plugins/eg01-amp.lv2/manifest.ttl.in @@ -0,0 +1,101 @@ +# 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 +# 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: . +@prefix rdfs: . + +# ==== Data ==== + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + +# The token `@LIB_EXT@` above is replaced by the build system with the +# appropriate extension for the current platform (e.g. .so, .dylib, .dll). +# This file is called called `manifest.ttl.in` rather than `manifest.ttl` +# to indicate that it is not the final file to be installed. +# This is not necessary, but is a good idea for portable plugins. +# For reability, the following text will assume `.so` is the extension used. +# +# In short, this declares that the resource with URI +# "http://lv2plug.in/plugins/eg-amp" is an LV2 plugin, with executable code in +# the file "amp.so" and a full description in "amp.ttl". These paths are +# relative to the bundle directory. +# +# There are 3 statements in this description: +# |================================================================ +# | Subject | Predicate | Object +# | | a | lv2:Plugin +# | | lv2:binary | +# | | rdfs:seeAlso | +# |================================================================ +# +# The semicolon is used to continue the previous subject; an equivalent +# but more verbose syntax for the same data is: + + a lv2:Plugin . + lv2:binary . + rdfs:seeAlso . + +# (Since this data is equivalent, it is safe, if pointless, to list it twice) +# +# 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 +# . 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. + + a lv2:Plugin . + +# The `a`, as in ``is a'', is a Turtle shortcut for `rdf:type`. +# `lv2:Plugin` expands to (using the +# `lv2:` prefix above) which is the type of all LV2 plugins. +# This statement means `` is an LV2 plugin''. + + lv2:binary . + +# This says "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. + + rdfs:seeAlso . + +# 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/waf b/plugins/eg01-amp.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg01-amp.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg01-amp.lv2/wscript b/plugins/eg01-amp.lv2/wscript new file mode 100644 index 0000000..d4295ff --- /dev/null +++ b/plugins/eg01-amp.lv2/wscript @@ -0,0 +1,67 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-amp.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Amp Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') + + conf.check(features='c cprogram', lib='m', uselib_store='M', mandatory=False) + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-amp.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-amp.lv2) + for i in ['amp.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = None + if autowaf.is_child: + includes = '../..' + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'amp.c', + name = 'amp', + target = '%s/amp' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'M LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg02-midigate.lv2/README.txt b/plugins/eg02-midigate.lv2/README.txt new file mode 100644 index 0000000..8f4a0f0 --- /dev/null +++ b/plugins/eg02-midigate.lv2/README.txt @@ -0,0 +1,10 @@ +== MIDI Gate == + +This plugin demonstrates: + + * Receiving MIDI input + + * Processing audio based on MIDI events with sample accuracy + + * Supporting MIDI programs which the host can control/automate, or present a + user interface for with human readable labels diff --git a/plugins/eg02-midigate.lv2/manifest.ttl.in b/plugins/eg02-midigate.lv2/manifest.ttl.in new file mode 100644 index 0000000..d32f1dc --- /dev/null +++ b/plugins/eg02-midigate.lv2/manifest.ttl.in @@ -0,0 +1,10 @@ +# The manifest.ttl file follows the same template as the previous example. + +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/plugins/eg02-midigate.lv2/midigate.c b/plugins/eg02-midigate.lv2/midigate.c new file mode 100644 index 0000000..3b74bfc --- /dev/null +++ b/plugins/eg02-midigate.lv2/midigate.c @@ -0,0 +1,223 @@ +/* + Copyright 2013 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include + +#include +#include + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" + +typedef enum { + MIDIGATE_CONTROL = 0, + MIDIGATE_IN = 1, + MIDIGATE_OUT = 2 +} PortIndex; + +typedef struct { + // Port buffers + const LV2_Atom_Sequence* control; + const float* in; + float* out; + + // Features + LV2_URID_Map* map; + + struct { + LV2_URID midi_MidiEvent; + } uris; + + unsigned n_active_notes; + unsigned program; +} Midigate; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + /** Scan features array for the URID feature we need. */ + LV2_URID_Map* map = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + map = (LV2_URID_Map*)features[i]->data; + break; + } + } + if (!map) { + /** + No URID feature given. This is a host bug since we require this + feature, but should be handled gracefully anyway. + */ + return NULL; + } + + Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); + self->map = map; + self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); + + return (LV2_Handle)self; +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Midigate* self = (Midigate*)instance; + + switch ((PortIndex)port) { + case MIDIGATE_CONTROL: + self->control = (const LV2_Atom_Sequence*)data; + break; + case MIDIGATE_IN: + self->in = (const float*)data; + break; + case MIDIGATE_OUT: + self->out = (float*)data; + break; + } +} + +static void +activate(LV2_Handle instance) +{ + Midigate* self = (Midigate*)instance; + self->n_active_notes = 0; + self->program = 0; +} + +/** + A function to write a chunk of output, to be called from run(). If the gate + is high, then the input will be passed through for this chunk, otherwise + silence is written. +*/ +static void +write_output(Midigate* self, uint32_t offset, uint32_t len) +{ + const bool active = (self->program == 0) + ? (self->n_active_notes > 0) + : (self->n_active_notes == 0); + if (active) { + memcpy(self->out + offset, self->in + offset, len * sizeof(float)); + } else { + memset(self->out + offset, 0, len * sizeof(float)); + } +} + +/** + This plugin works through the cycle in chunks starting at offset zero. The + +offset+ represents the current time within this this cycle, so + the output from 0 to +offset+ has already been written. + + MIDI events are read in a loop. In each iteration, the number of active + notes (on note on and note off) or the program (on program change) is + updated, then the output is written up until the current event time. Then + +offset+ is updated and the next event is processed. After the loop the + final chunk from the last event to the end of the cycle is emitted. + + This pattern of iterating over input events and writing output along the way + is a common idiom for writing sample accurate output based on event input. + + Note that this simple example simply writes input or zero for each sample + based on the gate. A serious implementation would need to envelope the + transition to avoid aliasing. +*/ +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Midigate* self = (Midigate*)instance; + uint32_t offset = 0; + + LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) { + if (ev->body.type == self->uris.midi_MidiEvent) { + const uint8_t* const msg = (const uint8_t*)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + ++self->n_active_notes; + break; + case LV2_MIDI_MSG_NOTE_OFF: + --self->n_active_notes; + break; + case LV2_MIDI_MSG_PGM_CHANGE: + if (msg[1] == 0 || msg[1] == 1) { + self->program = msg[1]; + } + break; + default: break; + } + } + + write_output(self, offset, ev->time.frames - offset); + offset = ev->time.frames; + } + + write_output(self, offset, sample_count - offset); +} + +/** + We have no resources to free on deactivation. + Note that the next call to activate will re-initialise the state, namely + self->n_active_notes, so there is no need to do so here. +*/ +static void +deactivate(LV2_Handle instance) +{} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + This plugin also has no extension data to return. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +static const LV2_Descriptor descriptor = { + MIDIGATE_URI, + instantiate, + connect_port, + activate, + run, + deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg02-midigate.lv2/midigate.ttl b/plugins/eg02-midigate.lv2/midigate.ttl new file mode 100644 index 0000000..59ac815 --- /dev/null +++ b/plugins/eg02-midigate.lv2/midigate.ttl @@ -0,0 +1,80 @@ +# The same set of namespace prefixes with two additions for LV2 extensions this +# plugin uses: atom and urid. + +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix midi: . +@prefix rdfs: . +@prefix urid: . + + + a lv2:Plugin ; + doap:name "Example MIDI Gate" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; +# Describe program banks so the host can automate and/or present a user +# interface. Describing supported programs (or any other MIDI event) is not +# required, but is a good idea since it allows hosts to make better use of +# plugins. This plugin has a single bank of two programs, which have a +# (mandatory) label, and an (optional) comment to describe their meaning in +# more detail. +# +# Both programs and the bank have an index, which corresponds to the MIDI bank +# and program numbers that will activate them. Since there are other ways to +# change programs (not used here), an index is not strictly required, but must +# be present to support program changes from MIDI. + lv2:bank [ + rdfs:label "Default" ; + lv2:index 0 ; + lv2:program [ + lv2:index 0 ; + rdfs:label "Normal" ; + rdfs:comment "Input is passed through if notes are active." + ] , [ + lv2:index 1 ; + rdfs:label "Inverted" ; + rdfs:comment "Input is passed through if no notes are active." + ] + ] ; +# This plugin has three ports. There is an audio input and output as before, +# as well as a new AtomPort. An AtomPort buffer contains an Atom, which is a +# generic container for any type of data. In this case, we want to receive +# MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is +# a series of events with time stamps. +# +# Events themselves are also generic and can contain any type of data, but in +# this case we are only interested in MIDI events. The (optional) +# +atom:supports+ property describes which event types are supported. Though +# not required, this information should always be given so the host knows what +# types of event it can expect the plugin to understand. +# +# The (optional) +lv2:designation+ of this port is +lv2:control+, which +# indicates that this is the "main" control port where the host should send +# events it expects to configure the plugin, in this case changing the MIDI +# program. This is necessary since it is possible to have several MIDI input +# ports, though typically it is best to have one. + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 1 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] . diff --git a/plugins/eg02-midigate.lv2/waf b/plugins/eg02-midigate.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg02-midigate.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg02-midigate.lv2/wscript b/plugins/eg02-midigate.lv2/wscript new file mode 100644 index 0000000..44336af --- /dev/null +++ b/plugins/eg02-midigate.lv2/wscript @@ -0,0 +1,65 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-midigate.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Midigate Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-midigate.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-midigate.lv2) + for i in ['midigate.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = None + if autowaf.is_child: + includes = '../..' + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'midigate.c', + name = 'midigate', + target = '%s/midigate' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg03-metro.lv2/README.txt b/plugins/eg03-metro.lv2/README.txt new file mode 100644 index 0000000..5e9a84a --- /dev/null +++ b/plugins/eg03-metro.lv2/README.txt @@ -0,0 +1,9 @@ +== Metronome == + +This plugin demonstrates tempo synchronisation by clicking on every beat. The +host sends this information to the plugin as events, so an event with new time +and tempo information will be received whenever there is a change. + +Time is assumed to continue rolling at the tempo and speed defined by the last +received tempo event, even across cycles, until a new tempo event is received +or the plugin is deactivated. diff --git a/plugins/eg03-metro.lv2/manifest.ttl.in b/plugins/eg03-metro.lv2/manifest.ttl.in new file mode 100644 index 0000000..bd93f66 --- /dev/null +++ b/plugins/eg03-metro.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/plugins/eg03-metro.lv2/metro.c b/plugins/eg03-metro.lv2/metro.c new file mode 100644 index 0000000..d2ac982 --- /dev/null +++ b/plugins/eg03-metro.lv2/metro.c @@ -0,0 +1,345 @@ +/* + LV2 Metronome Example Plugin + Copyright 2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include +#include +#include +#ifndef __cplusplus +# include +#endif + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/time/time.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#ifndef M_PI +# define M_PI 3.14159265 +#endif + +#define EG_METRO_URI "http://lv2plug.in/plugins/eg-metro" + +typedef struct { + LV2_URID atom_Blank; + LV2_URID atom_Float; + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID time_Position; + LV2_URID time_barBeat; + LV2_URID time_beatsPerMinute; + LV2_URID time_speed; +} MetroURIs; + +static const double attack_s = 0.005; +static const double decay_s = 0.075; + +enum { + METRO_CONTROL = 0, + METRO_NOTIFY = 1, + METRO_OUT = 2 +}; + +/** During execution this plugin can be in one of 3 states: */ +typedef enum { + STATE_ATTACK, // Envelope rising + STATE_DECAY, // Envelope lowering + STATE_OFF // Silent +} State; + +/** The plugin instance structure: */ +typedef struct { + LV2_URID_Map* map; // URID map feature + MetroURIs uris; // Cache of mapped URIDs + + struct { + LV2_Atom_Sequence* control; + LV2_Atom_Sequence* notify; + float* output; + } ports; + + /** The rate, bpm, and speed are the basic information sent by the host. */ + double rate; + float bpm; + float speed; + + /** To keep track of when to play the next click, we need to keep track of + a few pieces of information: */ + + /** - The frames since the start of the last click is stored */ + uint32_t elapsed_len; + + /** - The current play offset in the wave */ + uint32_t wave_offset; + + /** - The current play state (attack, decay, or off) */ + State state; + + /** The wave to play is a simple sine wave generated at instantiation time + based on the sample rate. The length in frames is stored in order to + continuously play the wave in a cycle to avoid discontinuity clicks. */ + float* wave; + uint32_t wave_len; + + /** The continuously playing sine wave is enveloped to provide an actual + metronome tick. This plugin 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. */ + uint32_t attack_len; + uint32_t decay_len; +} Metro; + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Metro* self = (Metro*)instance; + + switch (port) { + case METRO_CONTROL: + self->ports.control = (LV2_Atom_Sequence*)data; + break; + case METRO_NOTIFY: + self->ports.notify = (LV2_Atom_Sequence*)data; + break; + case METRO_OUT: + self->ports.output = (float*)data; + break; + default: + break; + } +} + +static void +activate(LV2_Handle instance) +{ + Metro* self = (Metro*)instance; + + self->elapsed_len = 0; + self->wave_offset = 0; + self->state = STATE_OFF; +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Metro* self = (Metro*)calloc(1, sizeof(Metro)); + if (!self) { + return NULL; + } + + /** Scan host features for URID map */ + LV2_URID_Map* map = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + map = (LV2_URID_Map*)features[i]->data; + } + } + if (!map) { + fprintf(stderr, "Host does not support urid:map.\n"); + free(self); + return NULL; + } + + /** Map URIS */ + MetroURIs* const uris = &self->uris; + self->map = map; + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); + uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); + uris->time_Position = map->map(map->handle, LV2_TIME__Position); + uris->time_barBeat = map->map(map->handle, LV2_TIME__barBeat); + uris->time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute); + uris->time_speed = map->map(map->handle, LV2_TIME__speed); + + /** Initialise fields */ + self->rate = rate; + self->bpm = 120.0f; + self->attack_len = attack_s * rate; + self->decay_len = 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 = (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; + } + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +static void +play(Metro* self, uint32_t begin, uint32_t end) +{ + float* const output = self->ports.output; + const uint32_t frames_per_beat = 60.0f / self->bpm * self->rate; + + if (self->speed == 0.0f) { + memset(output, 0, (end - begin) * sizeof(float)); + return; + } + + for (uint32_t i = begin; i < end; ++i) { + switch (self->state) { + case STATE_ATTACK: + /** Amplitude increases from 0..1 until attack_len */ + output[i] = self->wave[self->wave_offset] * + self->elapsed_len / (float)self->attack_len; + if (self->elapsed_len >= self->attack_len) { + self->state = STATE_DECAY; + } + break; + case STATE_DECAY: + /** Amplitude decreases from 1..0 until attack_len + decay_len */ + output[i] = 0.0f; + output[i] = self->wave[self->wave_offset] * + (1 - ((self->elapsed_len - self->attack_len) / + (float)self->decay_len)); + if (self->elapsed_len >= self->attack_len + self->decay_len) { + self->state = STATE_OFF; + } + break; + case STATE_OFF: + output[i] = 0.0f; + } + + /** We continuously play the sine wave regardless of envelope */ + self->wave_offset = (self->wave_offset + 1) % self->wave_len; + + /** Update elapsed time and start attack if necessary */ + if (++self->elapsed_len == frames_per_beat) { + self->state = STATE_ATTACK; + self->elapsed_len = 0; + } + } +} + +static void +update_position(Metro* self, const LV2_Atom_Object* obj) +{ + const MetroURIs* uris = &self->uris; + + /** Received new transport position/speed */ + LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; + lv2_atom_object_get(obj, + uris->time_barBeat, &beat, + uris->time_beatsPerMinute, &bpm, + uris->time_speed, &speed, + NULL); + if (bpm && bpm->type == uris->atom_Float) { + /** Tempo changed, update BPM */ + self->bpm = ((LV2_Atom_Float*)bpm)->body; + } + if (speed && speed->type == uris->atom_Float) { + /** Speed changed, e.g. 0 (stop) to 1 (play) */ + self->speed = ((LV2_Atom_Float*)speed)->body; + } + if (beat && beat->type == uris->atom_Float) { + /** Received a beat position, synchronise. + This is a simple hard sync that may cause clicks. + A real plugin would do something more graceful. + */ + const float frames_per_beat = 60.0f / self->bpm * self->rate; + const float bar_beats = ((LV2_Atom_Float*)beat)->body; + const float beat_beats = bar_beats - floorf(bar_beats); + self->elapsed_len = beat_beats * frames_per_beat; + if (self->elapsed_len < self->attack_len) { + self->state = STATE_ATTACK; + } else if (self->elapsed_len < self->attack_len + self->decay_len) { + self->state = STATE_DECAY; + } else { + self->state = STATE_OFF; + } + } +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Metro* self = (Metro*)instance; + const MetroURIs* uris = &self->uris; + + /** Empty notify output for now */ + LV2_Atom_Sequence* notify = self->ports.notify; + notify->atom.type = self->uris.atom_Sequence; + notify->atom.size = sizeof(LV2_Atom_Sequence_Body); + notify->body.unit = notify->body.pad = 0; + + /** 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); + !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; + if (obj->body.otype == uris->time_Position) { + /** Received position information, update */ + update_position(self, obj); + } + } + + /** Update time for next iteration and move to next event*/ + last_t = ev->time.frames; + } + + /** Play for remainder of cycle */ + play(self, last_t, sample_count); +} + +static const LV2_Descriptor descriptor = { + EG_METRO_URI, + instantiate, + connect_port, + activate, + run, + NULL, // deactivate, + cleanup, + NULL, // extension_data +}; + +LV2_SYMBOL_EXPORT const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/plugins/eg03-metro.lv2/metro.ttl b/plugins/eg03-metro.lv2/metro.ttl new file mode 100644 index 0000000..a6f297f --- /dev/null +++ b/plugins/eg03-metro.lv2/metro.ttl @@ -0,0 +1,39 @@ +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix time: . +@prefix urid: . + + + a lv2:Plugin ; + doap:name "Example Metronome" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; +# Since this port supports time:Position, the host knows to deliver time and +# tempo information + atom:supports time:Position ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" ; + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports ; + lv2:portProperty lv2:connectionOptional ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" ; + ] . diff --git a/plugins/eg03-metro.lv2/waf b/plugins/eg03-metro.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg03-metro.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg03-metro.lv2/wscript b/plugins/eg03-metro.lv2/wscript new file mode 100644 index 0000000..40642b6 --- /dev/null +++ b/plugins/eg03-metro.lv2/wscript @@ -0,0 +1,64 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-metro.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Metro Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='0.2.0', uselib_store='LV2') + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-metro.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-metro.lv2) + bld(features = 'subst', + is_copy = True, + source = 'metro.ttl', + target = '%s/metro.ttl' % bundle, + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'metro.c', + name = 'metro', + target = '%s/metro' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + diff --git a/plugins/eg04-sampler.lv2/README.txt b/plugins/eg04-sampler.lv2/README.txt new file mode 100644 index 0000000..c1cac46 --- /dev/null +++ b/plugins/eg04-sampler.lv2/README.txt @@ -0,0 +1 @@ +== Sampler == diff --git a/plugins/eg04-sampler.lv2/click.wav b/plugins/eg04-sampler.lv2/click.wav new file mode 100644 index 0000000..520a18c Binary files /dev/null and b/plugins/eg04-sampler.lv2/click.wav differ diff --git a/plugins/eg04-sampler.lv2/manifest.ttl.in b/plugins/eg04-sampler.lv2/manifest.ttl.in new file mode 100644 index 0000000..b4fa23e --- /dev/null +++ b/plugins/eg04-sampler.lv2/manifest.ttl.in @@ -0,0 +1,13 @@ +@prefix lv2: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + + + a ui:GtkUI ; + ui:binary ; + rdfs:seeAlso . diff --git a/plugins/eg04-sampler.lv2/sampler.c b/plugins/eg04-sampler.lv2/sampler.c new file mode 100644 index 0000000..5bb4e54 --- /dev/null +++ b/plugins/eg04-sampler.lv2/sampler.c @@ -0,0 +1,498 @@ +/* + LV2 Sampler Example Plugin + Copyright 2011-2012 David Robillard + Copyright 2011 Gabriel M. Beddingfield + Copyright 2011 James Morris + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file sampler.c Sampler Plugin + + A simple example of an LV2 sampler that dynamically loads a single sample + (based on incoming events) and triggers their playback (based on incoming + MIDI note events). + + This plugin illustrates: + - UI <=> Plugin communication via events + - Use of the worker extension for non-realtime tasks (sample loading) + - Use of the log extension to print log messages via the host + - Saving plugin state via the state extension + - Dynamic plugin control via the same properties saved to state +*/ + +#include +#include +#include +#ifndef __cplusplus +# include +#endif + +#include + +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/log/logger.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/ext/worker/worker.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "./uris.h" + +enum { + SAMPLER_CONTROL = 0, + SAMPLER_NOTIFY = 1, + SAMPLER_OUT = 2 +}; + +static const char* default_sample_file = "click.wav"; + +typedef struct { + SF_INFO info; /**< Info about sample from sndfile */ + float* data; /**< Sample data in float */ + char* path; /**< Path of file */ + 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.ttl b/plugins/eg04-sampler.lv2/sampler.ttl new file mode 100644 index 0000000..e008de0 --- /dev/null +++ b/plugins/eg04-sampler.lv2/sampler.ttl @@ -0,0 +1,83 @@ +# LV2 Sampler Example Plugin +# Copyright 2011-2012 David Robillard +# Copyright 2011 Gabriel M. Beddingfield +# Copyright 2011 James Morris +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix atom: . +@prefix doap: . +@prefix lv2: . +@prefix patch: . +@prefix rdfs: . +@prefix state: . +@prefix ui: . +@prefix urid: . +@prefix work: . + + + a lv2:Parameter ; + rdfs:label "sample" ; + rdfs:range atom:Path . + + + a lv2:Plugin ; + doap:name "Example Sampler" ; + doap:license ; + lv2:project ; + lv2:requiredFeature urid:map , + work:schedule ; + lv2:optionalFeature lv2:hardRTCapable , + state:loadDefaultState ; + lv2:extensionData state:interface , + work:interface ; + ui:ui ; + patch:writable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports , + patch:Message ; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 2 ; + lv2:symbol "out" ; + lv2:name "Out" + ] ; + state:state [ + + ] . + + + a ui:GtkUI ; + lv2:requiredFeature urid:map ; + ui:portNotification [ + ui:plugin ; + lv2:symbol "notify" ; + ui:notifyType atom:Blank + ] . diff --git a/plugins/eg04-sampler.lv2/sampler_ui.c b/plugins/eg04-sampler.lv2/sampler_ui.c new file mode 100644 index 0000000..40922ae --- /dev/null +++ b/plugins/eg04-sampler.lv2/sampler_ui.c @@ -0,0 +1,201 @@ +/* + LV2 Sampler Example Plugin UI + Copyright 2011-2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file ui.c Sampler Plugin UI +*/ + +#include + +#include + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#include "./uris.h" + +#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" + +typedef struct { + LV2_Atom_Forge forge; + + LV2_URID_Map* map; + SamplerURIs uris; + + LV2UI_Write_Function write; + LV2UI_Controller controller; + + GtkWidget* box; + GtkWidget* button; + GtkWidget* label; +} 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/uris.h b/plugins/eg04-sampler.lv2/uris.h new file mode 100644 index 0000000..e2ec6d0 --- /dev/null +++ b/plugins/eg04-sampler.lv2/uris.h @@ -0,0 +1,142 @@ +/* + LV2 Sampler Example Plugin + Copyright 2011-2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef SAMPLER_URIS_H +#define SAMPLER_URIS_H + +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" + +#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" +#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" +#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample" +#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample" + +typedef struct { + LV2_URID atom_Blank; + LV2_URID atom_Path; + LV2_URID atom_Resource; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + LV2_URID eg_applySample; + LV2_URID eg_sample; + LV2_URID eg_freeSample; + LV2_URID midi_Event; + LV2_URID patch_Set; + LV2_URID patch_property; + LV2_URID patch_value; +} SamplerURIs; + +static inline void +map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) +{ + uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); + uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); + uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); + uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); + uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); + uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); + uris->eg_applySample = map->map(map->handle, EG_SAMPLER__applySample); + uris->eg_freeSample = map->map(map->handle, EG_SAMPLER__freeSample); + uris->eg_sample = map->map(map->handle, EG_SAMPLER__sample); + uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); + uris->patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris->patch_property = map->map(map->handle, LV2_PATCH__property); + uris->patch_value = map->map(map->handle, LV2_PATCH__value); +} + +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 . + */ +static inline LV2_Atom* +write_set_file(LV2_Atom_Forge* forge, + const SamplerURIs* uris, + const char* filename, + const size_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_property_head(forge, uris->patch_property, 0); + lv2_atom_forge_urid(forge, uris->eg_sample); + lv2_atom_forge_property_head(forge, uris->patch_value, 0); + lv2_atom_forge_path(forge, filename, filename_len); + + lv2_atom_forge_pop(forge, &frame); + + return set; +} + +/** + * Get the file path from a message like: + * [] + * a patch:Set ; + * patch:property eg:sample ; + * patch:value . + */ +static inline const LV2_Atom* +read_set_file(const SamplerURIs* uris, + const LV2_Atom_Object* obj) +{ + if (obj->body.otype != uris->patch_Set) { + fprintf(stderr, "Ignoring unknown message type %d\n", obj->body.otype); + return NULL; + } + + /* Get property URI. */ + const LV2_Atom* property = NULL; + lv2_atom_object_get(obj, uris->patch_property, &property, 0); + if (!property) { + fprintf(stderr, "Malformed set message has no body.\n"); + return NULL; + } else if (property->type != uris->atom_URID) { + fprintf(stderr, "Malformed set message has non-URID property.\n"); + return NULL; + } else if (((LV2_Atom_URID*)property)->body != uris->eg_sample) { + fprintf(stderr, "Set message for unknown property.\n"); + return NULL; + } + + /* Get value. */ + const LV2_Atom* file_path = NULL; + lv2_atom_object_get(obj, uris->patch_value, &file_path, 0); + if (!file_path) { + fprintf(stderr, "Malformed set message has no value.\n"); + return NULL; + } else if (file_path->type != uris->atom_Path) { + fprintf(stderr, "Set message value is not a Path.\n"); + return NULL; + } + + return file_path; +} + +#endif /* SAMPLER_URIS_H */ diff --git a/plugins/eg04-sampler.lv2/waf b/plugins/eg04-sampler.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg04-sampler.lv2/waf @@ -0,0 +1 @@ +../../waf \ No newline at end of file diff --git a/plugins/eg04-sampler.lv2/wscript b/plugins/eg04-sampler.lv2/wscript new file mode 100644 index 0000000..732c904 --- /dev/null +++ b/plugins/eg04-sampler.lv2/wscript @@ -0,0 +1,80 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-sampler.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.set_c99_mode(conf) + autowaf.display_header('Sampler Configuration') + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') + + autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', + atleast_version='1.0.0', mandatory=True) + autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', + atleast_version='2.18.0', mandatory=False) + + autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) + print('') + +def build(bld): + bundle = 'eg-sampler.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-sampler.lv2) + for i in ['sampler.ttl', 'click.wav']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = ['.'] + if autowaf.is_child: + includes += ['../..'] + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'sampler.c', + name = 'sampler', + target = '%s/sampler' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'SNDFILE LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat + + # Build UI library + if bld.is_defined('HAVE_GTK2'): + obj = bld(features = 'c cshlib', + source = 'sampler_ui.c', + name = 'sampler_ui', + target = '%s/sampler_ui' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + use = 'GTK2 LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat -- cgit v1.2.1