{"id":42923307,"url":"https://github.com/fps/lv2-ttl2c","last_synced_at":"2026-01-30T18:02:51.262Z","repository":{"id":63708403,"uuid":"568806039","full_name":"fps/lv2-ttl2c","owner":"fps","description":"A small python script to generate code from a LV2 plugin bundle manifest","archived":false,"fork":false,"pushed_at":"2023-05-07T06:59:18.000Z","size":100,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-05-07T07:29:30.209Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-11-21T12:57:54.000Z","updated_at":"2023-05-07T07:29:30.210Z","dependencies_parsed_at":"2023-02-09T02:45:31.555Z","dependency_job_id":null,"html_url":"https://github.com/fps/lv2-ttl2c","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/fps/lv2-ttl2c","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fps%2Flv2-ttl2c","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fps%2Flv2-ttl2c/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fps%2Flv2-ttl2c/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fps%2Flv2-ttl2c/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fps","download_url":"https://codeload.github.com/fps/lv2-ttl2c/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fps%2Flv2-ttl2c/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28917033,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T16:37:38.804Z","status":"ssl_error","status_checked_at":"2026-01-30T16:37:37.878Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-30T18:02:48.761Z","updated_at":"2026-01-30T18:02:51.250Z","avatar_url":"https://github.com/fps.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# What?\n\nThis repository contains a little python script to make writing an LV2 plugin a little less repetitive/painful.\n\n# Requirements:\n\n- Python 3\n- regexec, if you want to rebuild the documentation (README.md): https://github.com/fps/regexec\n- valgrind and sord, if you want to run the tests\n\n# Usage\n\n\u003cpre\u003e\nusage: lv2-ttl2c [-h] [-b BUNDLE] [-o OUTPUT_DIRECTORY] [-p PREFIX]\n\nGenerate useful C code from a LV2 plugin bundle's metadata\n\noptions:\n  -h, --help            show this help message and exit\n  -b BUNDLE, --bundle BUNDLE\n                        the bundle directory to analyze (default: .)\n  -o OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY\n                        the output directory (default: .)\n  -p PREFIX, --prefix PREFIX\n                        the prefix added to output filenames (default: ttl2c_)\n\u003c/pre\u003e\n\nYou write the turle (ttl) files describing the plugins in your bundle and the python script then generates some useful \u003ccode\u003e#include\u003c/code\u003es 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 \u003ccode\u003elv2/\u003c/code\u003e directory)\n\n```C\n// This example is adapted from the eg-amp plugin shipped\n// with the LV2 distribution\n\n// Include the generated header\n#include \"generated/ttl2c_eg_amp.h\"\n\n// Implement the one callback necessary. Note how there is one type per port.\nstatic void run (\n    plugin_t *instance, uint32_t nframes, \n    const plugin_port_gain_t gain, \n    const plugin_port_in_t in, \n    const plugin_port_out_t out\n) {\n    for (uint32_t frame = 0; frame \u003c nframes; ++frame) {\n        // Each port type has a .data member which hold the\n        // connected data location:\n        out.data[frame] = gain.data * in.data[frame];\n    }\n}\n\n// We want run() to be run ;)\nstatic const plugin_callbacks_t plugin_callbacks = {\n    .run = run\n};\n\n// Include the generated C file\n#include \"generated/ttl2c_eg_amp.c\"\n\n\n```\n\n# How?\n\nPer plugin a \u003ccode\u003ebasename\u003c/code\u003e 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 \u003ccode\u003ehttp://lv2plug.in/plugins/eg-amp\u003c/code\u003e thus becomes \u003ccode\u003eeg_amp\u003c/code\u003e. The generated source and header file names then get constructed as \u003ccode\u003ettl2c_${basename}.[h|c]\u003c/code\u003e.\n\n# An example with state\n\nThis example can be found in the file `exp.c`.\n\n```C\n#include \"generated/ttl2c_eg_exp.h\"\n#include \u003cmath.h\u003e\n#include \u003cstdlib.h\u003e\n#include \u003cstring.h\u003e\n\n// This is our state. The name of the type is struct plugin_state\n// (the generated files assume this precise name):\ntypedef struct plugin_state {\n    float s;\n    float sampling_interval;\n} plugin_state_t;\n\n// The instantiate callback already gets a plugin_t *instance pointer\n// instead of an LV2_Handle and only needs to perform additional\n// initialisation.\nstatic plugin_t* instantiate (\n    plugin_t *instance, double sample_rate,\n    const char *bundle_path, const LV2_Feature *const *features\n) {\n    instance-\u003estate = malloc(sizeof(plugin_state_t));\n    memset(instance-\u003estate, 0, sizeof(plugin_state_t));\n    instance-\u003estate-\u003esampling_interval = 1.0f / sample_rate;\n    return instance;\n}\n\n// And similarly the cleanup callback only needs to care about\n// the additional deinitialisation (inverse of instantiate).\nstatic void cleanup (plugin_t *instance) {\n    free(instance-\u003estate);\n}\n\nstatic void run (\n    plugin_t *instance, uint32_t nframes, \n    const plugin_port_t1_t t1,\n    const plugin_port_in_t in,\n    const plugin_port_out_t out\n) {\n    plugin_state_t *state = instance-\u003estate;\n\n    const float a = 1.0f - expf(-state-\u003esampling_interval/t1.data);\n    for (uint32_t frame = 0; frame \u003c nframes; ++frame) {\n        out.data[frame] = in.data[frame] * a + state-\u003es * (1 - a);\n        state-\u003es = out.data[frame];\n    }\n}\n\nstatic const plugin_callbacks_t plugin_callbacks = {\n    .instantiate = instantiate,\n    .run = run,\n    .cleanup = cleanup,\n};\n\n#include \"generated/ttl2c_eg_exp.c\"\n\n\n```\n\n# An example processing some MIDI\n\nThis example can be found in the file `midigate.c`.\n\n```C\n// This example is adapted from the eg-midigate plugin shipped\n// with the LV2 distribution\n#include \"generated/ttl2c_eg_midigate.h\"\n#include \u003cstdlib.h\u003e\n#include \u003cstring.h\u003e\n\ntypedef struct plugin_state {\n    unsigned n_active_notes;\n    unsigned program; // 0 = normal, 1 = inverted\n} plugin_state_t;\n\nstatic plugin_t* instantiate (\n    plugin_t *instance, double sample_rate,\n    const char *bundle_path, const LV2_Feature *const *features\n) {\n    instance-\u003estate = malloc(sizeof(plugin_state_t));\n    memset(instance-\u003estate, 0, sizeof(plugin_state_t));\n    return instance;\n}\n\nstatic void write_output (\n    plugin_t* self, uint32_t offset, uint32_t len,\n    const plugin_port_in_t in,\n    const plugin_port_out_t out\n) {\n    plugin_state_t *state = self-\u003estate;\n\n    const bool active =\n        (state-\u003eprogram == 0) ? (state-\u003en_active_notes \u003e 0) : (state-\u003en_active_notes == 0);\n\n    if (active) {\n        memcpy(out.data + offset, in.data + offset, len * sizeof(float));\n    } else {\n        memset(out.data + offset, 0, len * sizeof(float));\n    }\n}\n\nstatic void run (\n    plugin_t *instance, uint32_t nframes, \n    const plugin_port_control_t control,\n    const plugin_port_in_t in,\n    const plugin_port_out_t out\n) {\n    plugin_state_t *state = instance-\u003estate;\n    uint32_t  offset = 0;\n\n    LV2_ATOM_SEQUENCE_FOREACH (control.data, ev) {\n        if (ev-\u003ebody.type == instance-\u003emidi_MidiEvent) {\n            const uint8_t* const msg = (const uint8_t*)(ev + 1);\n            switch (lv2_midi_message_type(msg)) {\n            case LV2_MIDI_MSG_NOTE_ON:\n                    ++state-\u003en_active_notes;\n                break;\n            case LV2_MIDI_MSG_NOTE_OFF:\n                if (state-\u003en_active_notes \u003e 0) {\n                    --state-\u003en_active_notes;\n                }\n                break;\n            case LV2_MIDI_MSG_CONTROLLER:\n                if (msg[1] == LV2_MIDI_CTL_ALL_NOTES_OFF) {\n                    state-\u003en_active_notes = 0;\n                }\n                break;\n            case LV2_MIDI_MSG_PGM_CHANGE:\n                if (msg[1] == 0 || msg[1] == 1) {\n                    state-\u003eprogram = msg[1];\n                }\n                break;\n            default:\n                break;\n            }\n        }\n\n        write_output(instance, offset, ev-\u003etime.frames - offset, in, out);\n        offset = (uint32_t)ev-\u003etime.frames;\n    }\n\n    write_output(instance, offset, nframes - offset, in, out);\n}\n\nstatic void cleanup(plugin_t *instance) {\n    free(instance-\u003estate);\n}\n\nstatic const plugin_callbacks_t plugin_callbacks = {\n    .instantiate = instantiate,\n    .run = run,\n    .cleanup = cleanup,\n};\n\n#include \"generated/ttl2c_eg_midigate.c\"\n\n\n```\n\n# Makefile\n\nHere is the makefile included with this project used to build and test the generated source:\n\n```make\n.PHONY: test clean all\n\n# EXTRA_CFLAGS ?= -march=native -mcpu=native -O3 -Wall -Werror -pedantic\nEXTRA_CFLAGS ?= -g -O1 -Wall -Werror -pedantic -fPIC\n\nLV2_TTL_PATH ?= /usr/lib/lv2\n\nall: plugins \n\nPLUGINS = amp exp midigate\nPLUGIN_LIBRARIES = $(PLUGINS:%=lv2/example.lv2/%.so)\n\nplugins: $(PLUGIN_LIBRARIES)\n\nlv2/example.lv2/%.so: %.c generated/done makefile\n\tgcc ${EXTRA_CFLAGS} $\u003c -shared -o $@\n\ngenerated/done: lv2/example.lv2/*.ttl lv2-ttl2c lv2_ttl2c/templates/*\n\t./lv2-ttl2c -b lv2/example.lv2 -o generated \n\ttouch generated/done\n\ntest: plugins\n\tlv2_validate ${PWD}/lv2/example.lv2/*.ttl 2\u003e\u00261\n\tLV2_PATH=${PWD}/lv2 lv2ls\n\tfor n in $(PLUGINS); do LV2_PATH=${PWD}/lv2 lv2info http://lv2plug.in/plugins/eg-\"$$n\"; done\n\tfor n in $(PLUGINS); do LV2_PATH=${PWD}/lv2 valgrind --leak-check=full --show-leak-kinds=all lv2bench http://lv2plug.in/plugins/eg-\"$$n\"; done\n\ndoc: README.md \n\nREADME.md: README.md.in *.c makefile generated/done\n\tcat README.md.in | regexec | regexec -e \"\\[usage\\]\" -c \"./lv2-ttl2c -h\" -n 1 \u003e README.md\n\nclean:\n\trm -f lv2/example.lv2/*.so generated/*.h generated/*.c generated/done lv2/example.lv2/done\n\n```\n\n# License\n\nGnu GPL v3\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffps%2Flv2-ttl2c","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffps%2Flv2-ttl2c","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffps%2Flv2-ttl2c/lists"}