[go: up one dir, main page]

Skip to content
/ lv2-ttl2c Public

A small python script to generate code from a LV2 plugin bundle manifest

License

Notifications You must be signed in to change notification settings

fps/lv2-ttl2c

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

What?

This repository contains a little python script to make writing an LV2 plugin a little less repetitive/painful.

Requirements:

  • Python 3
  • regexec, if you want to rebuild the documentation (README.md): https://github.com/fps/regexec
  • valgrind and sord, if you want to run the tests

Usage

usage: lv2-ttl2c [-h] [-b BUNDLE] [-o OUTPUT_DIRECTORY] [-p PREFIX]

Generate useful C code from a LV2 plugin bundle's metadata

options:
  -h, --help            show this help message and exit
  -b BUNDLE, --bundle BUNDLE
                        the bundle directory to analyze (default: .)
  -o OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY
                        the output directory (default: .)
  -p PREFIX, --prefix PREFIX
                        the prefix added to output filenames (default: ttl2c_)

You write the turle (ttl) files describing the plugins in your bundle and the python script then generates some useful #includes for you. Below you see the code that's necessary to write when running the script on the amp-plugin example from the lv2 distribution (included here for reference and testing in the lv2/ directory)

// This example is adapted from the eg-amp plugin shipped
// with the LV2 distribution

// Include the generated header
#include "generated/ttl2c_eg_amp.h"

// Implement the one callback necessary. Note how there is one type per port.
static void run (
    plugin_t *instance, uint32_t nframes, 
    const plugin_port_gain_t gain, 
    const plugin_port_in_t in, 
    const plugin_port_out_t out
) {
    for (uint32_t frame = 0; frame < nframes; ++frame) {
        // Each port type has a .data member which hold the
        // connected data location:
        out.data[frame] = gain.data * in.data[frame];
    }
}

// We want run() to be run ;)
static const plugin_callbacks_t plugin_callbacks = {
    .run = run
};

// Include the generated C file
#include "generated/ttl2c_eg_amp.c"

How?

Per plugin a basename is generated by splitting the plugin URI by path separators and just using the last part of the path. Some characters are replaced to make valid C identifiers. In the example http://lv2plug.in/plugins/eg-amp thus becomes eg_amp. The generated source and header file names then get constructed as ttl2c_${basename}.[h|c].

An example with state

This example can be found in the file exp.c.

#include "generated/ttl2c_eg_exp.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>

// This is our state. The name of the type is struct plugin_state
// (the generated files assume this precise name):
typedef struct plugin_state {
    float s;
    float sampling_interval;
} plugin_state_t;

// The instantiate callback already gets a plugin_t *instance pointer
// instead of an LV2_Handle and only needs to perform additional
// initialisation.
static plugin_t* instantiate (
    plugin_t *instance, double sample_rate,
    const char *bundle_path, const LV2_Feature *const *features
) {
    instance->state = malloc(sizeof(plugin_state_t));
    memset(instance->state, 0, sizeof(plugin_state_t));
    instance->state->sampling_interval = 1.0f / sample_rate;
    return instance;
}

// And similarly the cleanup callback only needs to care about
// the additional deinitialisation (inverse of instantiate).
static void cleanup (plugin_t *instance) {
    free(instance->state);
}

static void run (
    plugin_t *instance, uint32_t nframes, 
    const plugin_port_t1_t t1,
    const plugin_port_in_t in,
    const plugin_port_out_t out
) {
    plugin_state_t *state = instance->state;

    const float a = 1.0f - expf(-state->sampling_interval/t1.data);
    for (uint32_t frame = 0; frame < nframes; ++frame) {
        out.data[frame] = in.data[frame] * a + state->s * (1 - a);
        state->s = out.data[frame];
    }
}

static const plugin_callbacks_t plugin_callbacks = {
    .instantiate = instantiate,
    .run = run,
    .cleanup = cleanup,
};

#include "generated/ttl2c_eg_exp.c"

An example processing some MIDI

This example can be found in the file midigate.c.

// This example is adapted from the eg-midigate plugin shipped
// with the LV2 distribution
#include "generated/ttl2c_eg_midigate.h"
#include <stdlib.h>
#include <string.h>

typedef struct plugin_state {
    unsigned n_active_notes;
    unsigned program; // 0 = normal, 1 = inverted
} plugin_state_t;

static plugin_t* instantiate (
    plugin_t *instance, double sample_rate,
    const char *bundle_path, const LV2_Feature *const *features
) {
    instance->state = malloc(sizeof(plugin_state_t));
    memset(instance->state, 0, sizeof(plugin_state_t));
    return instance;
}

static void write_output (
    plugin_t* self, uint32_t offset, uint32_t len,
    const plugin_port_in_t in,
    const plugin_port_out_t out
) {
    plugin_state_t *state = self->state;

    const bool active =
        (state->program == 0) ? (state->n_active_notes > 0) : (state->n_active_notes == 0);

    if (active) {
        memcpy(out.data + offset, in.data + offset, len * sizeof(float));
    } else {
        memset(out.data + offset, 0, len * sizeof(float));
    }
}

static void run (
    plugin_t *instance, uint32_t nframes, 
    const plugin_port_control_t control,
    const plugin_port_in_t in,
    const plugin_port_out_t out
) {
    plugin_state_t *state = instance->state;
    uint32_t  offset = 0;

    LV2_ATOM_SEQUENCE_FOREACH (control.data, ev) {
        if (ev->body.type == instance->midi_MidiEvent) {
            const uint8_t* const msg = (const uint8_t*)(ev + 1);
            switch (lv2_midi_message_type(msg)) {
            case LV2_MIDI_MSG_NOTE_ON:
                    ++state->n_active_notes;
                break;
            case LV2_MIDI_MSG_NOTE_OFF:
                if (state->n_active_notes > 0) {
                    --state->n_active_notes;
                }
                break;
            case LV2_MIDI_MSG_CONTROLLER:
                if (msg[1] == LV2_MIDI_CTL_ALL_NOTES_OFF) {
                    state->n_active_notes = 0;
                }
                break;
            case LV2_MIDI_MSG_PGM_CHANGE:
                if (msg[1] == 0 || msg[1] == 1) {
                    state->program = msg[1];
                }
                break;
            default:
                break;
            }
        }

        write_output(instance, offset, ev->time.frames - offset, in, out);
        offset = (uint32_t)ev->time.frames;
    }

    write_output(instance, offset, nframes - offset, in, out);
}

static void cleanup(plugin_t *instance) {
    free(instance->state);
}

static const plugin_callbacks_t plugin_callbacks = {
    .instantiate = instantiate,
    .run = run,
    .cleanup = cleanup,
};

#include "generated/ttl2c_eg_midigate.c"

Makefile

Here is the makefile included with this project used to build and test the generated source:

.PHONY: test clean all

# EXTRA_CFLAGS ?= -march=native -mcpu=native -O3 -Wall -Werror -pedantic
EXTRA_CFLAGS ?= -g -O1 -Wall -Werror -pedantic -fPIC

LV2_TTL_PATH ?= /usr/lib/lv2

all: plugins 

PLUGINS = amp exp midigate
PLUGIN_LIBRARIES = $(PLUGINS:%=lv2/example.lv2/%.so)

plugins: $(PLUGIN_LIBRARIES)

lv2/example.lv2/%.so: %.c generated/done makefile
	gcc ${EXTRA_CFLAGS} $< -shared -o $@

generated/done: lv2/example.lv2/*.ttl lv2-ttl2c lv2_ttl2c/templates/*
	./lv2-ttl2c -b lv2/example.lv2 -o generated 
	touch generated/done

test: plugins
	lv2_validate ${PWD}/lv2/example.lv2/*.ttl 2>&1
	LV2_PATH=${PWD}/lv2 lv2ls
	for n in $(PLUGINS); do LV2_PATH=${PWD}/lv2 lv2info http://lv2plug.in/plugins/eg-"$$n"; done
	for n in $(PLUGINS); do LV2_PATH=${PWD}/lv2 valgrind --leak-check=full --show-leak-kinds=all lv2bench http://lv2plug.in/plugins/eg-"$$n"; done

doc: README.md 

README.md: README.md.in *.c makefile generated/done
	cat README.md.in | regexec | regexec -e "\[usage\]" -c "./lv2-ttl2c -h" -n 1 > README.md

clean:
	rm -f lv2/example.lv2/*.so generated/*.h generated/*.c generated/done lv2/example.lv2/done

License

Gnu GPL v3

About

A small python script to generate code from a LV2 plugin bundle manifest

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published