Browse Source

Generate book from example plugin source.

zrythm_meson
David Robillard 10 years ago
parent
commit
4a603a28de
  1. 26
      plugins/README.txt
  2. 21
      plugins/eg-amp.lv2/README.txt
  3. 117
      plugins/eg-amp.lv2/amp.c
  4. 92
      plugins/eg-amp.lv2/manifest.ttl.in
  5. 1
      plugins/eg-metro.lv2/README.txt
  6. 1
      plugins/eg-sampler.lv2/README.txt
  7. 113
      plugins/literasc.py
  8. 37
      plugins/wscript
  9. 12
      wscript

26
plugins/README.txt

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
= Programming LV2 Plugins =
David Robillard <d@drobilla.net>
:Author Initials: DER
:toc:
:website: http://lv2plug.in/
:doctype: book
== Introduction ==
This is a series of well-documented example plugins that demonstrate the various features of LV2.
Starting with the most basic plugin possible,
each adds new functionality and explains the features used from a high level perspective.
API and vocabulary reference documentation explains details,
but not the ``big picture''.
This book is intended to complement the reference documentation by providing good reference implementations of plugins,
while also conveying a higher-level understanding of LV2.
The chapters/plugins are arranged so that each builds incrementally on its predecessor.
Reading this book front to back is a good way to become familiar with modern LV2 programming.
The reader is expected to be familiar with C, but otherwise no special knowledge is required;
the first plugin describes the basics in detail.
This book is compiled from plugin source code into a single document for pleasant reading and ease of reference.
Each chapter corresponds to executable plugin code which can be found in the +plugins+ directory of the LV2 distribution.
If you prefer to read actual source code, all the content here is also available there, where the book text is included in comments.

21
plugins/eg-amp.lv2/README.txt

@ -0,0 +1,21 @@ @@ -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 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

117
plugins/eg-amp.lv2/amp.c

@ -15,30 +15,43 @@ @@ -15,30 +15,43 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
@file amp.c Implementation of the LV2 Amp example plugin.
This is a basic working LV2 plugin, about as small as one can get. It is
useful as a skeleton to copy to build more advanced plugins. See lv2.h for
more detailed descriptions of the rules for the various functions.
*/
/** Include standard C headers */
#include <math.h>
#include <stdlib.h>
#include <string.h>
/**
LV2 headers are based on the URI of the specification they come from, so a
consistent convention can be used even for unofficial extensions. The URI
of the core LV2 specification is <http://lv2plug.in/ns/lv2core>, 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"
/** Port indices. */
/**
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;
/** Plugin instance. */
/**
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;
@ -46,7 +59,16 @@ typedef struct { @@ -46,7 +59,16 @@ typedef struct {
float* output;
} Amp;
/** Create a new plugin instance. */
/**
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,
@ -58,7 +80,14 @@ instantiate(const LV2_Descriptor* descriptor, @@ -58,7 +80,14 @@ instantiate(const LV2_Descriptor* descriptor,
return (LV2_Handle)amp;
}
/** Connect a port to a buffer (audio thread, must be RT safe). */
/**
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,
@ -79,13 +108,21 @@ connect_port(LV2_Handle instance, @@ -79,13 +108,21 @@ connect_port(LV2_Handle instance,
}
}
/** Initialise and prepare the plugin instance for running. */
/**
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)
{
/* Nothing to do here in this trivial mostly stateless plugin. */
}
/** 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). */
@ -105,28 +142,55 @@ run(LV2_Handle instance, uint32_t n_samples) @@ -105,28 +142,55 @@ run(LV2_Handle instance, uint32_t n_samples)
}
}
/** Finish running (counterpart to activate()). */
/**
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)
{
/* Nothing to do here in this trivial mostly stateless plugin. */
}
/** Destroy a plugin instance (counterpart to instantiate()). */
/**
Destroy a plugin instance (counterpart to instantiate()).
This method is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.
*/
static void
cleanup(LV2_Handle instance)
{
free(instance);
}
/** Return extension data provided by the plugin. */
/**
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; /* This plugin has no extension data. */
return NULL;
}
/** The LV2_Descriptor for this plugin. */
/**
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,
@ -138,7 +202,16 @@ static const LV2_Descriptor descriptor = { @@ -138,7 +202,16 @@ static const LV2_Descriptor descriptor = {
extension_data
};
/** Entry point, the host will call this function to access descriptors. */
/**
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)

92
plugins/eg-amp.lv2/manifest.ttl.in

@ -9,34 +9,31 @@ @@ -9,34 +9,31 @@
# resources) are available. Manifest files should be as small as possible for
# performance reasons.
#
# The syntax of this file (and most other LV2 data files) is a language called
# Turtle ("Turse RDF Triple Language").[1] RDF[3] is a data model that
# expresses the relationship between things as (subject, predicate, object)
# triples. Turtle is a simple, terse, abbreviated syntax for RDF.
# Namespace Prefixes
#
# ==== 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
# <http://lv2plug.in/ns/lv2core#Plugin> the shorter form lv2:Plugin can be
# used. This is just a shorthand for URIs, the prefixes are not significant.
# can be defined. For example, with the `lv2:` prefix below, instead of
# <http://lv2plug.in/ns/lv2core#Plugin> the shorter form `lv2:Plugin` can be
# used. This is just a shorthand for URIs within a file, the prefixes are not
# significant otherwise.
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
# Data (list of resources in this bundle, hence "manifest")
# ==== Data ====
<http://lv2plug.in/plugins/eg-amp>
a lv2:Plugin ;
lv2:binary <amp@LIB_EXT@> ;
rdfs:seeAlso <amp.ttl> .
# Explanation
#
# The token @LIB_EXT@ above is replaced by the build system (waf) by the
# appropriate extension for the current platform (e.g. .so, .dylib, .dll),
# which is why this file is called manifest.ttl.in and not manifest.ttl. This
# documentation assumes .so for simplicity.
# 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
@ -44,12 +41,12 @@ @@ -44,12 +41,12 @@
# relative to the bundle directory.
#
# There are 3 statements in this description:
#
# # | Subject | Predicate | Object
# -------------------------------------------------------------------
# 1 | <http://lv2plug.in/plugins/eg-amp> | a | lv2:Plugin
# 2 | <http://lv2plug.in/plugins/eg-amp> | lv2:binary | <amp.so>
# 3 | <http://lv2plug.in/plugins/eg-amp> | rdfs:seeAlso | <amp.ttl>
# |================================================================
# | Subject | Predicate | Object
# | <http://lv2plug.in/plugins/eg-amp> | a | lv2:Plugin
# | <http://lv2plug.in/plugins/eg-amp> | lv2:binary | <amp.so>
# | <http://lv2plug.in/plugins/eg-amp> | rdfs:seeAlso | <amp.ttl>
# |================================================================
#
# The semicolon is used to continue the previous subject; an equivalent
# but more verbose syntax for the same data is:
@ -69,7 +66,8 @@ @@ -69,7 +66,8 @@
# 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 LV2 specification[4] for details.
# 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,
@ -80,32 +78,24 @@ @@ -80,32 +78,24 @@
# If this is truly impossible, use a URN, e.g. urn:myplugs:superamp.
#
# A detailed explanation of each statement follows.
#
# 1: <http://lv2plug.in/plugins/eg-amp> a lv2:Plugin
#
# The "a" is a Turtle shortcut for rdf:type and more or less means "is a".
# lv2:Plugin expands to <http://lv2plug.in/ns/lv2core#Plugin> (using the
# "lv2:" prefix above) and is the established URI for the type "LV2 Plugin".
# This statement literally means "this resource is an LV2 plugin".
#
# 2: <http://lv2plug.in/plugins/eg-amp> lv2:binary <amp.so>
#
# 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.
#
# 3: <http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl>
#
# This says "there is more information about this plugin located in the file
# "amp.ttl". The host will look at all such files when it needs to actually
# use or investigate the plugin.
# Footnotes
#
# [1] http://www.w3.org/TeamSubmission/turtle/
# [2] http://www.w3.org/RDF/
# http://www.w3.org/TR/2004/REC-rdf-primer-20040210/
# [3] http://tools.ietf.org/html/rfc3986
# [4] http://lv2plug.in/ns/lv2core
<http://lv2plug.in/plugins/eg-amp> a lv2:Plugin .
# The `a` is a Turtle shortcut for rdf:type and more or less means ``is a''.
# `lv2:Plugin` expands to <http://lv2plug.in/ns/lv2core#Plugin> (using the
# `lv2:` prefix above) which is the type of all LV2 plugins.
# This statement means ``<http://lv2plug.in/plugins/eg-amp> is an LV2 plugin''.
<http://lv2plug.in/plugins/eg-amp> lv2:binary <amp@LIB_EXT@> .
# This says "this plugin has executable code ("binary") in the file
# named "amp.so", which is located in this bundle. The LV2 specification
# defines that all relative URIs in manifest files are relative to the bundle
# directory, so this refers to the file amp.so in the same directory as this
# manifest.ttl file.
<http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl> .
# This says ``there is more information about this plugin located in the file
# `amp.ttl`''. The host will look at all such files when it needs to actually
# use or investigate the plugin.

1
plugins/eg-metro.lv2/README.txt

@ -0,0 +1 @@ @@ -0,0 +1 @@
== Metronome ==

1
plugins/eg-sampler.lv2/README.txt

@ -0,0 +1 @@ @@ -0,0 +1 @@
== Sampler ==

113
plugins/literasc.py

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Literasc, a simple literate programming tool for C, C++, and Turtle.
# Copyright 2012 David Robillard <d@drobilla.net>
#
# Unlike many LP tools, this tool uses normal source code as input, there is no
# tangle/weave and no special file format. The literate parts of the program
# are written in comments, which are emitted as paragraphs of regular text
# interleaved with code. Asciidoc is both the comment and output syntax.
import os
import re
import sys
def format_text(text):
'Format a text (comment) fragment and return it as a marked up string'
return '\n\n' + re.sub('\n *', '\n', text.strip()) + '\n\n'
def format_code(lang, code):
if code.strip() == '':
return code
head = '[source,%s]' % lang
sep = '-' * len(head) + '\n'
return head + '\n' + sep + code.strip('\n') + '\n' + sep
def format_c_source(filename, file):
output = '=== %s ===\n' % os.path.basename(filename)
chunk = ''
prev_c = 0
in_comment = False
in_comment_start = False
n_stars = 0
code = ''
for line in file:
code += line
for c in code:
if prev_c == '/' and c == '*':
output += format_code('c', chunk[0:len(chunk)-1])
in_comment = True
in_comment_start = True
n_stars = 1
chunk = ''
elif in_comment and prev_c == '*' and c == '/':
if n_stars > 2:
output += format_text(chunk[0:len(chunk)-1])
in_comment = False
in_comment_start = False
chunk = ''
elif in_comment_start and c == '*':
n_stars += 1
else:
chunk += c
prev_c = c
return output + format_code('c', chunk)
def format_ttl_source(filename, file):
output = '=== %s ===\n' % os.path.basename(filename)
in_comment = False
chunk = ''
for line in file:
is_comment = line.strip().startswith('#')
if in_comment:
if is_comment:
chunk += line.strip().lstrip('# ') + ' \n'
else:
output += format_text(chunk)
in_comment = False
chunk = line
else:
if is_comment:
output += format_code('txt', chunk)
in_comment = True
chunk = line.strip().lstrip('# ') + ' \n'
else:
chunk += line
if in_comment:
return output + format_text(chunk)
else:
return output + format_code('txt', chunk)
def gen(out, filenames):
for filename in filenames:
file = open(filename)
if not file:
sys.stderr.write('Failed to open file %s\n' % filename)
continue
if filename.endswith('.c'):
out.write(format_c_source(filename, file))
elif filename.endswith('.ttl') or filename.endswith('.ttl.in'):
out.write(format_ttl_source(filename, file))
elif filename.endswith('.txt'):
for line in file:
out.write(line)
out.write('\n')
else:
sys.stderr.write("Unknown source format `%s'" % (
filename[filename.find('.'):]))
file.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write('Usage: %s FILENAME...\n' % sys.argv[1])
sys.exit(1)
gen(sys.argv[1:])

37
plugins/wscript

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
#!/usr/bin/env python
import os
from waflib.extras import autowaf as autowaf
import waflib.Logs as Logs
import literasc
def confgure(conf):
pass
def bld_book_src(task):
filenames = []
for i in task.inputs:
filenames += [i.abspath()]
literasc.gen(open(task.outputs[0].abspath(), 'w'), filenames)
def build(bld):
files = [bld.path.find_node('README.txt')]
for i in bld.path.ant_glob('*', src=False, dir=True):
for j in bld.path.ant_glob('%s/*.*' % i):
name = j.abspath()
if (name.endswith('.c') or
name.endswith('.txt') or
name.endswith('.ttl.in')):
files += [j]
bld(rule = bld_book_src,
source = files,
target = 'book.txt')
bld(rule = 'asciidoc -b html -o ${TGT} ${SRC}',
source = 'book.txt',
target = 'book.html')

12
wscript

@ -55,6 +55,13 @@ def configure(conf): @@ -55,6 +55,13 @@ def configure(conf):
conf.env.COPY_HEADERS = Options.options.copy_headers
conf.env.ONLINE_DOCS = Options.options.online_docs
if conf.env.DOCS or conf.env.ONLINE_DOCS:
try:
conf.find_program('asciidoc')
conf.env.BUILD_BOOK = True
except:
Logs.warn('Asciidoc not found, book will not be built')
# Check for gcov library (for test coverage)
if conf.env.BUILD_TESTS and not conf.is_defined('HAVE_GCOV'):
conf.check_cc(lib='gcov', define_name='HAVE_GCOV', mandatory=False)
@ -65,7 +72,7 @@ def configure(conf): @@ -65,7 +72,7 @@ def configure(conf):
conf.env.LV2_BUILD = ['lv2/lv2plug.in/ns/lv2core']
if conf.env.BUILD_PLUGINS:
for i in conf.path.ant_glob('plugins/*', dir=True):
for i in conf.path.ant_glob('plugins/*', src=False, dir=True):
try:
conf.recurse(i.srcpath())
conf.env.LV2_BUILD += [i.srcpath()]
@ -308,6 +315,9 @@ def build(bld): @@ -308,6 +315,9 @@ def build(bld):
for i in bld.env.LV2_BUILD:
bld.recurse(i)
if bld.env.BUILD_BOOK:
bld.recurse('plugins')
if bld.env.DOCS or bld.env.ONLINE_DOCS:
# Build Doxygen documentation (and tags file)
autowaf.build_dox(bld, 'LV2', VERSION, top, out, 'lv2plug.in/doc')

Loading…
Cancel
Save