Compare commits

..

25 Commits

Author SHA1 Message Date
Christopher Arndt 9474b4481e feat: add parameter range support
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-10-16 17:21:45 +02:00
Christopher Arndt b37954a27b fix: use FAUSTFLOAT macro in Nim faust wrapper
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-10-16 17:21:39 +02:00
Christopher Arndt 9b33bf10bf feat: generate faust C code and Nim wrapper with faustpp
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-10-08 19:42:50 +02:00
Christopher Arndt aa3bae40fb fix: don't use fastmath
FAUST fastmath implementation causes error with GCC >= 14.2.1:

    fastmath.cpp:212:33: error: initializer element is not constant

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-10-02 20:18:59 +02:00
Christopher Arndt 0a7c8c1730 docs: add See Also section to readme
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-30 10:40:06 +02:00
Christopher Arndt 38dc236740 docs: update how-to instructions in readme for changed file names
Link to Nim sourec of each example

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-30 09:26:46 +02:00
Christopher Arndt e6c66ed72c refactor: do param bounds checking in C code
Some more code re-arranging

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-30 09:25:24 +02:00
Christopher Arndt f40df2491e refactor: clean up faustlpf plugin Nim code a bit
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-29 16:33:18 +02:00
Christopher Arndt 4dcfd1abdb docs: update readme
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-29 14:18:07 +02:00
Christopher Arndt 1f72523055 fix: use -fvisibility=hidden when using GCC
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-29 13:49:43 +02:00
Christopher Arndt 521fefc531 feat: add faustlpf example
Shows how to use DSP code in C generated from FAUST code

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-29 13:47:26 +02:00
Christopher Arndt eb814d16c3 fix: export all enum members in core and atom API 2024-09-28 03:02:49 +02:00
Christopher Arndt f001184fea feat: add tiltfilter example 2024-09-28 03:02:49 +02:00
Christopher Arndt 75f3ce03b3 refactor: some file and object renaming
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-09-28 03:02:07 +02:00
Christopher Arndt 4d60684b07 Turn atomSequenceClear into a template
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-28 15:48:39 +02:00
Christopher Arndt f71ec0d0df Remove comment not relevant to Nim version
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-28 15:32:16 +02:00
Christopher Arndt b88f75efce Use `isNil` to check null pointers
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-28 15:31:29 +02:00
Christopher Arndt af27abbaca Replace C memcmp/cpy with copyMem/cmpMem
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-23 20:17:13 +02:00
Christopher Arndt 1dcf5ac82f Minor doc comment fixes
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-23 18:15:52 +02:00
Christopher Arndt 11c9fc653d Some Atom handling refactoring
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-22 19:14:34 +02:00
Christopher Arndt d39cea5a98 Update readme to mention miditranspose example 2024-05-21 18:13:42 +02:00
Christopher Arndt 9bb9321dcb Make MIDI transpose example functional
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-21 18:09:40 +02:00
Christopher Arndt ba6aaa22ab Implement MIDI procesing (WIP)
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-20 13:20:01 +02:00
Christopher Arndt ff6ddf77d2 Move wrapper code into sub-module; adapt/tweak examples
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-18 15:11:02 +02:00
Christopher Arndt 095774213d fix: use correct prefix for units LV2 extension in manifests
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-05-01 05:21:05 +02:00
37 changed files with 2353 additions and 167 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ nimble.paths
*.so *.so
*.dll *.dll
/.lv2/ /.lv2/
*.code-workspace

View File

@ -1,6 +1,7 @@
# nymph # nymph
A [Nim] library for writing audio and MIDI plugins conforming to the [LV2] standard A [Nim] library for writing audio and MIDI plugins conforming to the [LV2]
standard
## Examples ## Examples
@ -22,53 +23,68 @@ Install the `amp.lv2` example plugin:
cp -a examples/amp.lv2 ~/.lv2 cp -a examples/amp.lv2 ~/.lv2
Other example plugins can be found in the [examples](./examples) directory and Other example plugins can be found in the [examples](./examples) directory and
can be built and tested with similar commands, just changing the example name can be built and tested with similar commands. Just change the example name to
to the basename of the plugin's LV2 bundle dir. the basename of the plugin's LV2 bundle dir.
Currently, there is only one other example plugin, `multimode_filter`, but Currently, there are just a few other example plugins:
more will be added soon.
* [`miditranspose`](./examples/miditranspose_plugin.nim): shows how to handle
receiving and sending MIDI events.
* [`multimodefilter`](./examples/multimodefilter_plugin.nim): shows a
multimode state-variable filter implementation ported from C++ to Nim.
* [`tiltfilter`](./examples/titltfilter_plugin.nim): shows a multimode tilt
equalizer filter implementation ported from Rust to Nim.
* [`faustlpf`](./examples/faustlpf_plugin.nim): shows how to integrate DSP C
code generated from a [FAUST] source file in Nim (in this example the FAUST
code implements a simple non-resonant low-pass filter from the FAUST
standard library).
## How To ## How To
* Install this library: **Note:** I'll use `mydsp` as the base name for the new plugin in the
examples below. Substitute your own plugin basename wherever you see it used
below.
nimble install https://git.0x20.eu/chris/nymph 1. Install this library:
* Copy `examples/amp.lv2` and `examples/amp.nim` into your project and rename nimble install https://github.com/SpotlightKid/nymph.git
them as you like (also rename `amp.lv2/amp.ttl`). I'll use `myplugin` as the
base name in the examples below.
* Edit `myplugin.lv2/manifest.ttl`: 1. Make a directory named `mydsp.lv2` and copy `examples/amp.lv2/manifest.ttl`
* Change the plugin URI. into it. Also copy `examples/amp.lv2/amp.ttl` to `mydsp.lv2/mydsp.ttl`.
* Change the plugin's shared library name defined via `lv2:binary` to
`libmyplugin.so`.
* Change file name referenced via `rdfs:seeAlso` to `myplugin.ttl`.
* Edit `myplugin.lv2/myplugin.ttl`: 1. Copy `examples/amp_plugin.nim` into your project as `mydsp_plugin.nim`.
* Change the plugin URI.
* Define audio, control and atom ports as needed.
* Edit `myplugin.nim`: 1. Edit `mydsp.lv2/manifest.ttl`:
* Change the `PluginUri` constant at the top. * Change the plugin URI.
* Change the `PluginPort` enum and list the ports in the order defined in * Change the plugin's shared library name defined via `lv2:binary` to
`myplugin.ttl`. `libmydsp.so`.
* Rename and update the definition of the `AmpPlugin` object type and * Change file name referenced via `rdfs:seeAlso` to `mydsp.ttl`.
define its members with the appropriate data type for the plugin port
they will be connected to. Update the type name in the `instantiate`,
`deactivate`, `connectPorts` and `run` procs.
* Update and extend the `case` statement in `connectPort` to connect ports
to your plugin object instance members.
* Implement your DSP code in the `run` proc.
* Compile the plugin shared library object with: 1. Edit `mydsp.lv2/mydsp.ttl`:
* Change the plugin URI.
* Define audio, control and atom ports as needed.
1. Edit `mydsp_plugin.nim`:
* Change the `PluginUri` constant at the top.
* Change the `PluginPort` enum and list the ports in the order defined in
`mydsp.ttl`.
* Rename and update the definition of the `AmpPlugin` object type and
define its members with the appropriate data types for the plugin ports
they will be connected to. Update the type name in the `instantiate`,
`deactivate`, `connectPorts` and `run` procs.
* Update and extend the `case` statement in `connectPort` to connect ports
to your plugin object instance members.
* Implement your DSP code in the `run` proc.
1. Compile the plugin shared library object with:
nim c --app:lib --noMain:on --mm:arc \ nim c --app:lib --noMain:on --mm:arc \
--out:myplugin.lv2/libmyplugin.so myplugin.nim --out:mydsp.lv2/libmydsp.so mydsp_plugin.nim
See the definition of the `build_ex` task in the See the definition of the `build_ex` task in the
[nymph.nimble](./nymph.nimble#L43) file on how to create a nimble task [nymph.nimble](./nymph.nimble#L67) file on how to create a nimble task
to simplify compilation. to simplify compilation and additional compiler args you might want to use.
## Dependencies ## Dependencies
@ -83,8 +99,17 @@ Optional:
* [lv2lint] - For checking conformity of plugin bundles * [lv2lint] - For checking conformity of plugin bundles
## See Also
* [lv2-nim](https://gitlab.com/lpirl/lv2-nim) - Older third-party Nim bindings
for the LV2 audio plugin specification. Last update in 2021.
* [offbeat](https://github.com/NimAudio/offbeat) - A [CLAP] based plugin
framework in Nim
[CLAP]: https://cleveraudio.org/
[FAUST]: https://faust.grame.fr/
[LV2]: https://lv2plug.in/ [LV2]: https://lv2plug.in/
[lv2bm]: https://github.com/moddevices/lv2bm [lv2bm]: https://github.com/moddevices/lv2bm
[lv2lint]: https://git.open-music-kontrollers.ch/~hp/lv2lint [lv2lint]: https://git.open-music-kontrollers.ch/~hp/lv2lint
[Nim]: https://nim-lang.org/ [Nim]: https://nim-lang.org/

View File

@ -1,14 +1,14 @@
@prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> . @prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> .
@prefix doap: <http://usefulinc.com/ns/doap#> . @prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> . @prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> . @prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> . @prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix params: <http://lv2plug.in/ns/ext/parameters#> . @prefix params: <http://lv2plug.in/ns/ext/parameters#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix unit: <http://lv2plug.in/ns/extensions/units#> . @prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:nymph:examples:amp> <urn:nymph:examples:amp>
a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ; a lv2:Plugin , lv2:AmplifierPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ; lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
@ -36,7 +36,7 @@
lv2:default 0.0 ; lv2:default 0.0 ;
lv2:minimum -90.0 ; lv2:minimum -90.0 ;
lv2:maximum 20.0 ; lv2:maximum 20.0 ;
unit:unit unit:db ; units:unit units:db ;
]; ];
rdfs:comment """ rdfs:comment """

View File

@ -1,4 +1,4 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> . @prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:amp> <urn:nymph:examples:amp>

View File

@ -22,9 +22,12 @@ template db2coeff(db: cfloat): cfloat =
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble; proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
bundlePath: cstring; features: ptr ptr Lv2Feature): bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
Lv2Handle {.cdecl.} = Lv2Handle {.cdecl.} =
return createShared(AmpPlugin) try:
return createShared(AmpPlugin)
except OutOfMemDefect:
return nil
proc connectPort(instance: Lv2Handle; port: cuint; proc connectPort(instance: Lv2Handle; port: cuint;
@ -78,4 +81,3 @@ proc lv2Descriptor(index: cuint): ptr Lv2Descriptor {.
result.deactivate = deactivate result.deactivate = deactivate
result.cleanup = cleanup result.cleanup = cleanup
result.extensionData = extensionData result.extensionData = extensionData

View File

@ -1,48 +0,0 @@
## Dynamic Smoothing Using Self Modulating Filter
## Andrew Simper, Cytomic, 2014, andy@cytomic.com
## public release: 6th Dec 2016
## https://cytomic.com/files/dsp/DynamicSmoothing.pdf
type
DynParamSmooth* = object
baseFreq: float
sensitivity: float
wc: float
low1: float
low2: float
inz: float
proc reset*(self: var DynParamSmooth) =
self.low1 = 0.0
self.low2 = 0.0
self.inz = 0.0
proc setSampleRate*(self: var DynParamSmooth, sampleRate: float) =
self.wc = self.baseFreq / sampleRate
self.reset()
proc process*(self: var DynParamSmooth, sample: float): float =
let low1z = self.low1
let low2z = self.low2
let bandz = low1z - low2z
let wd = self.wc + self.sensitivity * abs(bandz)
let g = min(wd * (5.9948827 + wd * (-11.969296 + wd * 15.959062)), 1.0)
self.low1 = low1z + g * (0.5 * (sample + self.inz) - low1z)
self.low2 = low2z + g * (0.5 * (self.low1 + low1z) - low2z)
self.inz = sample
return self.low2
proc initDynParamSmooth*(
baseFreq: float = 2.0,
sensitivity: float = 2.0,
sampleRate: float = 48_000.0
): DynParamSmooth =
result.baseFreq = baseFreq
result.sensitivity = sensitivity
result.wc = result.baseFreq / sampleRate
result.reset()

396
examples/faustlpf.c Normal file
View File

@ -0,0 +1,396 @@
//------------------------------------------------------------------------------
// This file was generated using the Faust compiler (https://faust.grame.fr),
// and the Faust post-processor (https://github.com/SpotlightKid/faustpp).
//
// Source: faustlpf.dsp
// Name: FaustLPF
// Author: Christopher Arndt
// Copyright: Christopher Arndt, 2024
// License: MIT
// Version: 0.1.0
// FAUST version: 2.75.10
// FAUST compilation options: -a /home/chris/tmp/tmpebxwoqng.c -lang c -rui -ct 1 -fm def -cn faustlpf -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 -vec -lv 0 -vs 32
//------------------------------------------------------------------------------
#include "faustlpf.h"
//------------------------------------------------------------------------------
// Begin the Faust code section
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
// START INTRINSICS
// END INTRINSICS
// START CLASS CODE
#ifndef FAUSTFLOAT
#define FAUSTFLOAT float
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_WIN32)
#define RESTRICT __restrict
#else
#define RESTRICT __restrict__
#endif
#include "faust/dsp/fastmath.cpp"
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
static float faustlpf_faustpower2_f(float value) {
return value * value;
}
#ifndef FAUSTCLASS
#define FAUSTCLASS faustlpf
#endif
#ifdef __APPLE__
#define exp10f __exp10f
#define exp10 __exp10
#endif
faustlpf* newfaustlpf() {
faustlpf* dsp = (faustlpf*)calloc(1, sizeof(faustlpf));
return dsp;
}
void deletefaustlpf(faustlpf* dsp) {
free(dsp);
}
void metadatafaustlpf(MetaGlue* m) {
m->declare(m->metaInterface, "author", "Christopher Arndt");
m->declare(m->metaInterface, "compile_options", "-a /home/chris/tmp/tmpebxwoqng.c -lang c -rui -ct 1 -fm def -cn faustlpf -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 -vec -lv 0 -vs 32");
m->declare(m->metaInterface, "copyright", "Christopher Arndt, 2024");
m->declare(m->metaInterface, "filename", "faustlpf.dsp");
m->declare(m->metaInterface, "filters.lib/fir:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/fir:copyright", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/fir:license", "MIT-style STK-4.3 license");
m->declare(m->metaInterface, "filters.lib/iir:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/iir:copyright", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/iir:license", "MIT-style STK-4.3 license");
m->declare(m->metaInterface, "filters.lib/lowpass0_highpass1", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/lowpass0_highpass1:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/lowpass:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/lowpass:copyright", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/lowpass:license", "MIT-style STK-4.3 license");
m->declare(m->metaInterface, "filters.lib/name", "Faust Filters Library");
m->declare(m->metaInterface, "filters.lib/tf2:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/tf2:copyright", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/tf2:license", "MIT-style STK-4.3 license");
m->declare(m->metaInterface, "filters.lib/tf2s:author", "Julius O. Smith III");
m->declare(m->metaInterface, "filters.lib/tf2s:copyright", "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>");
m->declare(m->metaInterface, "filters.lib/tf2s:license", "MIT-style STK-4.3 license");
m->declare(m->metaInterface, "filters.lib/version", "1.3.0");
m->declare(m->metaInterface, "license", "MIT");
m->declare(m->metaInterface, "maths.lib/author", "GRAME");
m->declare(m->metaInterface, "maths.lib/copyright", "GRAME");
m->declare(m->metaInterface, "maths.lib/license", "LGPL with exception");
m->declare(m->metaInterface, "maths.lib/name", "Faust Math Library");
m->declare(m->metaInterface, "maths.lib/version", "2.8.0");
m->declare(m->metaInterface, "name", "FaustLPF");
m->declare(m->metaInterface, "platform.lib/name", "Generic Platform Library");
m->declare(m->metaInterface, "platform.lib/version", "1.3.0");
m->declare(m->metaInterface, "signals.lib/name", "Faust Signal Routing Library");
m->declare(m->metaInterface, "signals.lib/version", "1.6.0");
m->declare(m->metaInterface, "version", "0.1.0");
}
int getSampleRatefaustlpf(faustlpf* RESTRICT dsp) {
return dsp->fSampleRate;
}
int getNumInputsfaustlpf(faustlpf* RESTRICT dsp) {
return 1;
}
int getNumOutputsfaustlpf(faustlpf* RESTRICT dsp) {
return 1;
}
void classInitfaustlpf(int sample_rate) {
}
void instanceResetUserInterfacefaustlpf(faustlpf* dsp) {
dsp->fHslider0 = (FAUSTFLOAT)(1.5e+04f);
}
void instanceClearfaustlpf(faustlpf* dsp) {
/* C99 loop */
{
int l0;
for (l0 = 0; l0 < 4; l0 = l0 + 1) {
dsp->fRec1_perm[l0] = 0.0f;
}
}
/* C99 loop */
{
int l1;
for (l1 = 0; l1 < 4; l1 = l1 + 1) {
dsp->fRec0_perm[l1] = 0.0f;
}
}
}
void instanceConstantsfaustlpf(faustlpf* dsp, int sample_rate) {
dsp->fSampleRate = sample_rate;
dsp->fConst0 = fminf(1.92e+05f, fmaxf(1.0f, (float)(dsp->fSampleRate)));
dsp->fConst1 = 44.1f / dsp->fConst0;
dsp->fConst2 = 1.0f - dsp->fConst1;
dsp->fConst3 = 3.1415927f / dsp->fConst0;
}
void instanceInitfaustlpf(faustlpf* dsp, int sample_rate) {
instanceConstantsfaustlpf(dsp, sample_rate);
instanceResetUserInterfacefaustlpf(dsp);
instanceClearfaustlpf(dsp);
}
void initfaustlpf(faustlpf* dsp, int sample_rate) {
classInitfaustlpf(sample_rate);
instanceInitfaustlpf(dsp, sample_rate);
}
void buildUserInterfacefaustlpf(faustlpf* dsp, UIGlue* ui_interface) {
ui_interface->openVerticalBox(ui_interface->uiInterface, "FaustLPF");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "1", "");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "scale", "log");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "style", "knob");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "symbol", "cutoff");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "tooltip", "Low-pass filter cutoff frequency");
ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "unit", "Hz");
ui_interface->addHorizontalSlider(ui_interface->uiInterface, "Cutoff", &dsp->fHslider0, (FAUSTFLOAT)1.5e+04f, (FAUSTFLOAT)16.0f, (FAUSTFLOAT)1.5e+04f, (FAUSTFLOAT)0.1f);
ui_interface->closeBox(ui_interface->uiInterface);
}
void computefaustlpf(faustlpf* dsp, int count, FAUSTFLOAT** RESTRICT inputs, FAUSTFLOAT** RESTRICT outputs) {
FAUSTFLOAT* input0_ptr = inputs[0];
FAUSTFLOAT* output0_ptr = outputs[0];
float fSlow0 = dsp->fConst1 * fmaxf(16.0f, fminf(1.5e+04f, (float)(dsp->fHslider0)));
float fRec1_tmp[36];
float* fRec1 = &fRec1_tmp[4];
float fZec0[32];
float fZec1[32];
float fZec2[32];
float fRec0_tmp[36];
float* fRec0 = &fRec0_tmp[4];
int vindex = 0;
/* Main loop */
for (vindex = 0; vindex <= (count - 32); vindex = vindex + 32) {
FAUSTFLOAT* input0 = &input0_ptr[vindex];
FAUSTFLOAT* output0 = &output0_ptr[vindex];
int vsize = 32;
/* Recursive loop 0 */
/* Pre code */
/* C99 loop */
{
int j0;
for (j0 = 0; j0 < 4; j0 = j0 + 1) {
fRec1_tmp[j0] = dsp->fRec1_perm[j0];
}
}
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fRec1[i] = fSlow0 + dsp->fConst2 * fRec1[i - 1];
}
}
/* Post code */
/* C99 loop */
{
int j1;
for (j1 = 0; j1 < 4; j1 = j1 + 1) {
dsp->fRec1_perm[j1] = fRec1_tmp[vsize + j1];
}
}
/* Vectorizable loop 1 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec0[i] = fast_tanf(dsp->fConst3 * fRec1[i]);
}
}
/* Vectorizable loop 2 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec1[i] = 1.0f / fZec0[i];
}
}
/* Vectorizable loop 3 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec2[i] = (fZec1[i] + 1.4142135f) / fZec0[i] + 1.0f;
}
}
/* Recursive loop 4 */
/* Pre code */
/* C99 loop */
{
int j2;
for (j2 = 0; j2 < 4; j2 = j2 + 1) {
fRec0_tmp[j2] = dsp->fRec0_perm[j2];
}
}
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fRec0[i] = (float)(input0[i]) - (fRec0[i - 2] * ((fZec1[i] + -1.4142135f) / fZec0[i] + 1.0f) + 2.0f * fRec0[i - 1] * (1.0f - 1.0f / faustlpf_faustpower2_f(fZec0[i]))) / fZec2[i];
}
}
/* Post code */
/* C99 loop */
{
int j3;
for (j3 = 0; j3 < 4; j3 = j3 + 1) {
dsp->fRec0_perm[j3] = fRec0_tmp[vsize + j3];
}
}
/* Vectorizable loop 5 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
output0[i] = (FAUSTFLOAT)((fRec0[i - 2] + fRec0[i] + 2.0f * fRec0[i - 1]) / fZec2[i]);
}
}
}
/* Remaining frames */
if (vindex < count) {
FAUSTFLOAT* input0 = &input0_ptr[vindex];
FAUSTFLOAT* output0 = &output0_ptr[vindex];
int vsize = count - vindex;
/* Recursive loop 0 */
/* Pre code */
/* C99 loop */
{
int j0;
for (j0 = 0; j0 < 4; j0 = j0 + 1) {
fRec1_tmp[j0] = dsp->fRec1_perm[j0];
}
}
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fRec1[i] = fSlow0 + dsp->fConst2 * fRec1[i - 1];
}
}
/* Post code */
/* C99 loop */
{
int j1;
for (j1 = 0; j1 < 4; j1 = j1 + 1) {
dsp->fRec1_perm[j1] = fRec1_tmp[vsize + j1];
}
}
/* Vectorizable loop 1 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec0[i] = fast_tanf(dsp->fConst3 * fRec1[i]);
}
}
/* Vectorizable loop 2 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec1[i] = 1.0f / fZec0[i];
}
}
/* Vectorizable loop 3 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fZec2[i] = (fZec1[i] + 1.4142135f) / fZec0[i] + 1.0f;
}
}
/* Recursive loop 4 */
/* Pre code */
/* C99 loop */
{
int j2;
for (j2 = 0; j2 < 4; j2 = j2 + 1) {
fRec0_tmp[j2] = dsp->fRec0_perm[j2];
}
}
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
fRec0[i] = (float)(input0[i]) - (fRec0[i - 2] * ((fZec1[i] + -1.4142135f) / fZec0[i] + 1.0f) + 2.0f * fRec0[i - 1] * (1.0f - 1.0f / faustlpf_faustpower2_f(fZec0[i]))) / fZec2[i];
}
}
/* Post code */
/* C99 loop */
{
int j3;
for (j3 = 0; j3 < 4; j3 = j3 + 1) {
dsp->fRec0_perm[j3] = fRec0_tmp[vsize + j3];
}
}
/* Vectorizable loop 5 */
/* Compute code */
/* C99 loop */
{
int i;
for (i = 0; i < vsize; i = i + 1) {
output0[i] = (FAUSTFLOAT)((fRec0[i - 2] + fRec0[i] + 2.0f * fRec0[i - 1]) / fZec2[i]);
}
}
}
}
#ifdef __cplusplus
}
#endif
// END CLASS CODE
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
//------------------------------------------------------------------------------
// End the Faust code section

12
examples/faustlpf.dsp Normal file
View File

@ -0,0 +1,12 @@
declare name "FaustLPF";
declare author "Christopher Arndt";
declare copyright "Christopher Arndt, 2024";
declare license "MIT";
declare version "0.1.0";
import("stdfaust.lib");
cutoff = hslider("[1] Cutoff [symbol:cutoff] [unit:Hz] [scale:log] [style:knob] [tooltip:Low-pass filter cutoff frequency]",
15000, 16, 15000, 0.1) : si.smoo;
process = fi.lowpass(2, cutoff);

216
examples/faustlpf.h Normal file
View File

@ -0,0 +1,216 @@
#ifndef __faustlpf_H__
#define __faustlpf_H__
#ifndef FAUSTFLOAT
#define FAUSTFLOAT float
#endif
#ifndef FAUSTCLASS
#define FAUSTCLASS faustlpf
#endif
#if defined(_WIN32)
#define RESTRICT __restrict
#else
#define RESTRICT __restrict__
#endif
#include <stdbool.h>
#include <stdint.h>
#include "faust/gui/CInterface.h"
typedef struct {
int fSampleRate;
float fConst0;
float fConst1;
float fConst2;
FAUSTFLOAT fHslider0;
float fRec1_perm[4];
float fConst3;
float fRec0_perm[4];
} faustlpf;
faustlpf* newfaustlpf();
void deletefaustlpf(faustlpf* dsp);
void metadatafaustlpf(MetaGlue* m);
int getSampleRatefaustlpf(faustlpf* RESTRICT dsp);
int getNumInputsfaustlpf(faustlpf* RESTRICT dsp);
int getNumOutputsfaustlpf(faustlpf* RESTRICT dsp);
void classInitfaustlpf(int sample_rate);
void instanceResetUserInterfacefaustlpf(faustlpf* dsp);
void instanceClearfaustlpf(faustlpf* dsp);
void instanceConstantsfaustlpf(faustlpf* dsp, int sample_rate);
void instanceInitfaustlpf(faustlpf* dsp, int sample_rate);
void initfaustlpf(faustlpf* dsp, int sample_rate);
void buildUserInterfacefaustlpf(faustlpf* dsp, UIGlue* ui_interface);
void computefaustlpf(faustlpf* dsp, int count, FAUSTFLOAT** RESTRICT inputs, FAUSTFLOAT** RESTRICT outputs);
typedef struct {
FAUSTFLOAT init;
FAUSTFLOAT min;
FAUSTFLOAT max;
} ParameterRange;
int parameter_group(unsigned index) {
switch (index) {
case 0:
return 0;
default:
return -1;
}
}
const char *parameter_label(unsigned index) {
switch (index) {
case 0:
return "Cutoff";
default:
return 0;
}
}
const char *parameter_short_label(unsigned index) {
switch (index) {
case 0:
return "Cutoff";
default:
return 0;
}
}
const char *parameter_style(unsigned index) {
switch (index) {
case 0: {
return "knob";
}
default:
return "";
}
}
const char *parameter_symbol(unsigned index) {
switch (index) {
case 0:
return "cutoff";
default:
return "";
}
}
const char *parameter_unit(unsigned index) {
switch (index) {
case 0:
return "Hz";
default:
return 0;
}
}
const ParameterRange *parameter_range(unsigned index) {
switch (index) {
case 0: {
static const ParameterRange range = { 15000.0, 16.0, 15000.0 };
return &range;
}
default:
return 0;
}
}
bool parameter_is_trigger(unsigned index) {
switch (index) {
default:
return false;
}
}
bool parameter_is_boolean(unsigned index) {
switch (index) {
default:
return false;
}
}
bool parameter_is_enum(unsigned index) {
switch (index) {
default:
return false;
}
}
bool parameter_is_integer(unsigned index) {
switch (index) {
default:
return false;
}
}
bool parameter_is_logarithmic(unsigned index) {
switch (index) {
case 0:
return true;
default:
return false;
}
}
FAUSTFLOAT get_parameter(faustlpf* dsp, unsigned index) {
switch (index) {
case 0:
return dsp->fHslider0;
default:
(void)dsp;
return 0.0;
}
}
void set_parameter(faustlpf* dsp, unsigned index, FAUSTFLOAT value) {
switch (index) {
case 0:
dsp->fHslider0 = value;
break;
default:
(void)dsp;
(void)value;
break;
}
}
FAUSTFLOAT get_cutoff(faustlpf* dsp) {
return dsp->fHslider0;
}
void set_cutoff(faustlpf* dsp, FAUSTFLOAT value) {
dsp->fHslider0 = value;
}
#endif /* __faustlpf_H__ */

View File

@ -0,0 +1,58 @@
@prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix params: <http://lv2plug.in/ns/ext/parameters#> .
@prefix props: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:nymph:examples:faustlpf>
a lv2:Plugin , lv2:FilterPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
opts:supportedOption bufs:nominalBlockLength ,
bufs:maxBlockLength ,
params:sampleRate ;
lv2:port [
a lv2:InputPort, lv2:AudioPort ;
lv2:index 0 ;
lv2:name "Audio In" ;
lv2:symbol "in" ;
],
[
a lv2:OutputPort, lv2:AudioPort ;
lv2:index 1 ;
lv2:name "Audio Out" ;
lv2:symbol "out" ;
],
[
a lv2:InputPort, lv2:ControlPort ;
lv2:index 2 ;
lv2:name "Cutoff" ;
lv2:symbol "cutoff" ;
lv2:default 15000.0 ;
lv2:minimum 16.0 ;
lv2:maximum 15000.0 ;
lv2:portProperty props:logarithmic ;
units:unit units:hz ;
];
rdfs:comment """
A 2-pole lowpass filter from the FAUST standard library.
""" ;
doap:name "nymph faust LPF" ;
doap:license <https://spdx.org/licenses/MIT> ;
doap:maintainer [
foaf:name "Christopher Arndt" ;
foaf:mbox <mailto:info@chrisarndt.de> ;
foaf:homepage <https://gitlab.com/SpotlightKid/nymph> ;
] ;
lv2:microVersion 0 ;
lv2:minorVersion 1 .

View File

@ -0,0 +1,8 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:faustlpf>
a lv2:Plugin ;
lv2:binary <libfaustlpf.so> ;
rdfs:seeAlso <faustlpf.ttl> .

36
examples/faustlpf.nim Normal file
View File

@ -0,0 +1,36 @@
{.compile: "faustlpf.c".}
type
faustlpf* = object
ParameterRange* = object
init*, min*, max*: cfloat
SampleBuffer* = UncheckedArray[cfloat]
proc newfaustlpf*(): ptr faustlpf {.importc.}
proc deletefaustlpf*(dsp: ptr faustlpf) {.importc.}
proc initfaustlpf*(dsp: ptr faustlpf, sample_rate: cint) {.importc.}
proc instanceClearfaustlpf*(dsp: ptr faustlpf) {.importc.}
proc computefaustlpf*(dsp: ptr faustlpf, count: cint, inputs, outputs: ptr ptr SampleBuffer) {.importc.}
proc parameter_range*(index: cuint): ptr ParameterRange {.importc.}
proc parameter_group*(index: cuint): cint {.importc}
proc parameter_is_boolean*(index: cuint): bool {.importc}
proc parameter_is_enum*(index: cuint): bool {.importc}
proc parameter_is_integer*(index: cuint): bool {.importc}
proc parameter_is_logarithmic*(index: cuint): bool {.importc}
proc parameter_is_trigger*(index: cuint): bool {.importc}
proc parameter_label*(index: cuint): cstring {.importc}
proc parameter_short_label*(index: cuint): cstring {.importc}
proc parameter_style*(index: cuint): cstring {.importc}
proc parameter_symbol*(index: cuint): cstring {.importc}
proc parameter_unit*(index: cuint): cstring {.importc}
proc get_parameter*(dsp: ptr faustlpf, index: cuint): cfloat {.importc}
proc set_parameter*(dsp: ptr faustlpf, index: cuint, value: cfloat) {.importc}
proc get_cutoff*(dsp: ptr faustlpf): cfloat {.importc}
proc set_cutoff*(dsp: ptr faustlpf, value: cfloat) {.importc}

View File

@ -0,0 +1,84 @@
## A FAUST standard library 2-pole lowpass filter LV2 plugin
import nymph
import faustlpf
const
PluginUri = "urn:nymph:examples:faustlpf"
type
PluginPort {.pure.} = enum
Input, Output, Frequency
FaustLPFPlugin = object
input: ptr SampleBuffer
output: ptr SampleBuffer
freq: ptr cfloat
flt: ptr faustlpf
proc NimMain() {.cdecl, importc.}
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
Lv2Handle {.cdecl.} =
try:
let plug = cast[ptr FaustLPFPlugin](createShared(FaustLPFPlugin))
plug.flt = newfaustlpf()
initfaustlpf(plug.flt, sampleRate.cint)
return cast[Lv2Handle](plug)
except OutOfMemDefect:
return nil
proc connectPort(instance: Lv2Handle; port: cuint;
dataLocation: pointer) {.cdecl.} =
let plug = cast[ptr FaustLPFPlugin](instance)
case cast[PluginPort](port)
of PluginPort.Input:
plug.input = cast[ptr SampleBuffer](dataLocation)
of PluginPort.Output:
plug.output = cast[ptr SampleBuffer](dataLocation)
of PluginPort.Frequency:
plug.freq = cast[ptr cfloat](dataLocation)
proc activate(instance: Lv2Handle) {.cdecl.} =
let plug = cast[ptr FaustLPFPlugin](instance)
instanceClearfaustlpf(plug.flt)
proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} =
let plug = cast[ptr FaustLPFPlugin](instance)
plug.flt.set_cutoff(plug.freq[])
computefaustlpf(plug.flt, nSamples.cint, addr plug.input, addr plug.output)
proc deactivate(instance: Lv2Handle) {.cdecl.} =
discard
proc cleanup(instance: Lv2Handle) {.cdecl.} =
let plug = cast[ptr FaustLPFPlugin](instance)
deletefaustlpf(plug.flt)
freeShared(plug)
proc extensionData(uri: cstring): pointer {.cdecl.} =
return nil
proc lv2Descriptor(index: cuint): ptr Lv2Descriptor {.
cdecl, exportc, dynlib, extern: "lv2_descriptor".} =
NimMain()
if index == 0:
result = createShared(Lv2Descriptor)
result.uri = cstring(PluginUri)
result.instantiate = instantiate
result.connectPort = connectPort
result.activate = activate
result.run = run
result.deactivate = deactivate
result.cleanup = cleanup
result.extensionData = extensionData

View File

@ -0,0 +1,8 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:miditranspose>
a lv2:Plugin ;
lv2:binary <libmiditranspose.so> ;
rdfs:seeAlso <miditranspose.ttl> .

View File

@ -0,0 +1,66 @@
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix params: <http://lv2plug.in/ns/ext/parameters#> .
@prefix props: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
<urn:nymph:examples:miditranspose>
a lv2:Plugin , lv2:MIDIPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
lv2:requiredFeature urid:map ;
opts:supportedOption bufs:nominalBlockLength ,
bufs:maxBlockLength ,
params:sampleRate ;
lv2:port [
a lv2:InputPort , atom:AtomPort ;
lv2:index 0 ;
atom:bufferType atom:Sequence ;
atom:supports midi:MidiEvent ;
lv2:designation lv2:control ;
lv2:symbol "midi_in" ;
lv2:name "MIDI In"
] ,
[
a lv2:OutputPort , atom:AtomPort ;
lv2:index 1 ;
atom:bufferType atom:Sequence ;
atom:supports midi:MidiEvent ;
lv2:symbol "midi_out" ;
lv2:name "MIDI Out"
] ,
[
a lv2:InputPort, lv2:ControlPort ;
lv2:index 2 ;
lv2:name "Transposition" ;
lv2:symbol "transposition" ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum -12 ;
lv2:maximum 12 ;
units:unit units:semitone12TET
] ;
rdfs:comment "A simple MIDI transposition LV2 plugin." ;
doap:name "nymph miditranspose" ;
doap:license <https://spdx.org/licenses/MIT> ;
doap:maintainer [
foaf:name "Christopher Arndt" ;
foaf:mbox <mailto:info@chrisarndt.de> ;
foaf:homepage <https://gitlab.com/SpotlightKid/nymph> ;
] ;
lv2:microVersion 0 ;
lv2:minorVersion 1 .

View File

@ -0,0 +1,111 @@
## A simple MIDI transpose LV2 plugin
import std/math
#import std/strformat
#import std/strutils
import nymph/[atom, core, midi, util, urid]
import nymph/atom/util
const PluginUri = "urn:nymph:examples:miditranspose"
type
PluginPort {.pure.} = enum
Input, Output, Transposition
MidiTransposePlugin = object
input: ptr AtomSequence
output: ptr AtomSequence
transposition: ptr cfloat
map: ptr UridMap
midi_urid: Urid
MidiEvent = object
size: uint32
frames: int64
data: ptr UncheckedArray[byte]
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
Lv2Handle {.cdecl.} =
let amp: ptr MidiTransposePlugin = createShared(MidiTransposePlugin)
amp.map = cast[ptr UridMap](lv2FeaturesData(features, lv2UridMap))
if amp.map.isNil:
freeShared(amp)
return nil
amp.midi_urid = amp.map.map(amp.map.handle, lv2MidiMidiEvent)
return cast[Lv2Handle](amp)
proc connectPort(instance: Lv2Handle; port: cuint;
dataLocation: pointer) {.cdecl.} =
let amp = cast[ptr MidiTransposePlugin](instance)
case cast[PluginPort](port)
of PluginPort.Input:
amp.input = cast[ptr AtomSequence](dataLocation)
of PluginPort.Output:
amp.output = cast[ptr AtomSequence](dataLocation)
of PluginPort.Transposition:
amp.transposition = cast[ptr cfloat](dataLocation)
proc activate(instance: Lv2Handle) {.cdecl.} =
discard
proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} =
let amp = cast[ptr MidiTransposePlugin](instance)
let outCapacity = amp.output.atom.size
atomSequenceClear(amp.output)
amp.output.atom.type = amp.input.atom.type
if not atomSequenceIsEmpty(amp.input):
#echo &"Event sequence size: {amp.input.atom.size}"
let noteOffset = clamp(floor(amp.transposition[] + 0.5), -12, 12).uint8
for ev in amp.input:
if ev.body.`type` == amp.midi_urid:
var msg = cast[ptr UncheckedArray[uint8]](atomContents(AtomEvent, ev))
#echo &"0x{toHex(msg[0], 2)} 0x{toHex(msg[1], 2)} 0x{toHex(msg[2], 2)}"
case midiGetMessageType(msg[]):
of midiMsgNoteOff, midiMsgNoteOn, midiMsgNotePressure:
msg[1] = clamp(msg[1] + noteOffset, 0, 127).uint8
else:
discard
discard atomSequenceAppendEvent(amp.output, outCapacity, ev)
proc deactivate(instance: Lv2Handle) {.cdecl.} =
discard
proc cleanup(instance: Lv2Handle) {.cdecl.} =
freeShared(cast[ptr MidiTransposePlugin](instance))
proc extensionData(uri: cstring): pointer {.cdecl.} =
return nil
proc NimMain() {.cdecl, importc.}
proc lv2Descriptor(index: cuint): ptr Lv2Descriptor {.
cdecl, exportc, dynlib, extern: "lv2_descriptor".} =
NimMain()
if index == 0:
result = createShared(Lv2Descriptor)
result.uri = cstring(PluginUri)
result.instantiate = instantiate
result.connectPort = connectPort
result.activate = activate
result.run = run
result.deactivate = deactivate
result.cleanup = cleanup
result.extensionData = extensionData

19
examples/minarch.h Normal file
View File

@ -0,0 +1,19 @@
/******************* BEGIN minarch.h ****************/
/************************************************************************
FAUST Architecture File for generating a very minimal C interface
************************************************************************/
#include "faust/gui/CInterface.h"
/******************************************************************************
VECTOR INTRINSICS
*******************************************************************************/
<<includeIntrinsic>>
/**************************BEGIN USER SECTION **************************/
<<includeclass>>
/***************************END USER SECTION ***************************/

View File

@ -1,8 +0,0 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:multimode-filter>
a lv2:Plugin ;
lv2:binary <libmultimode_filter.so> ;
rdfs:seeAlso <multimode_filter.ttl> .

View File

@ -0,0 +1,8 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:multimodefilter>
a lv2:Plugin ;
lv2:binary <libmultimodefilter.so> ;
rdfs:seeAlso <multimodefilter.ttl> .

View File

@ -1,16 +1,16 @@
@prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> . @prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> .
@prefix doap: <http://usefulinc.com/ns/doap#> . @prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> . @prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> . @prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> . @prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix params: <http://lv2plug.in/ns/ext/parameters#> . @prefix params: <http://lv2plug.in/ns/ext/parameters#> .
@prefix props: <http://lv2plug.in/ns/ext/port-props#> . @prefix props: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix unit: <http://lv2plug.in/ns/extensions/units#> . @prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:nymph:examples:multimode-filter> <urn:nymph:examples:multimodefilter>
a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ; a lv2:Plugin , lv2:FilterPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ; lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
@ -39,7 +39,7 @@
lv2:minimum 16.0 ; lv2:minimum 16.0 ;
lv2:maximum 7000.0 ; lv2:maximum 7000.0 ;
lv2:portProperty props:logarithmic; lv2:portProperty props:logarithmic;
unit:unit unit:hz ; units:unit units:hz ;
], ],
[ [
a lv2:InputPort, lv2:ControlPort ; a lv2:InputPort, lv2:ControlPort ;

View File

@ -2,10 +2,10 @@
import nymph import nymph
import dynparamsmooth import paramsmooth
import svf import svf
const PluginUri = "urn:nymph:examples:multimode-filter" const PluginUri = "urn:nymph:examples:multimodefilter"
type type
SampleBuffer = UncheckedArray[cfloat] SampleBuffer = UncheckedArray[cfloat]
@ -19,17 +19,20 @@ type
cutoff: ptr cfloat cutoff: ptr cfloat
q: ptr cfloat q: ptr cfloat
mode: ptr cfloat mode: ptr cfloat
svf: FilterSV svf: SVFilter
smoothCutoff: DynParamSmooth smoothCutoff: ParamSmooth
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble; proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
bundlePath: cstring; features: ptr ptr Lv2Feature): bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
Lv2Handle {.cdecl.} = Lv2Handle {.cdecl.} =
let plug = createShared(SVFPlugin) try:
plug.svf = initFilterSV(fmLowPass, sampleRate) let plug = createShared(SVFPlugin)
plug.smoothCutoff = initDynParamSmooth(sampleRate=sampleRate) plug.svf = initSVFilter(fmLowPass, sampleRate)
return plug plug.smoothCutoff = initParamSmooth(20.0, sampleRate)
return cast[Lv2Handle](plug)
except OutOfMemDefect:
return nil
proc connectPort(instance: Lv2Handle; port: cuint; proc connectPort(instance: Lv2Handle; port: cuint;

View File

@ -24,7 +24,7 @@ proc setSampleRate*(self: var ParamSmooth, sampleRate: float64) =
self.z = 0.0 self.z = 0.0
proc process*(self: var ParamSmooth, sample: float): float = proc process*(self: var ParamSmooth, sample: float): float {.inline.} =
self.z = (sample * self.b) + (self.z * self.a) self.z = (sample * self.b) + (self.z * self.a)
return self.z return self.z

View File

@ -9,32 +9,32 @@ type
FilterMode* = enum FilterMode* = enum
fmLowPass, fmHighPass, fmBandPass, fmBandReject fmLowPass, fmHighPass, fmBandPass, fmBandReject
FilterSV* = object SVFilter* = object
mode: FilterMode mode: FilterMode
cutoff, q, lowPass, hiPass, bandPass, bandReject, a, b, maxCutoff: float cutoff, q, lowPass, hiPass, bandPass, bandReject, a, b, maxCutoff: float
sampleRate: float64 sampleRate: float64
needs_update: bool needsUpdate: bool
proc reset*(self: var FilterSV) = proc reset*(self: var SVFilter) =
self.lowPass = 0.0 self.lowPass = 0.0
self.hiPass = 0.0 self.hiPass = 0.0
self.bandPass = 0.0 self.bandPass = 0.0
self.bandReject = 0.0 self.bandReject = 0.0
proc initFilterSV*(mode: FilterMode = fmLowPass, sampleRate: float64 = 48_000.0): FilterSV = proc initSVFilter*(mode: FilterMode = fmLowPass, sampleRate: float64 = 48_000.0): SVFilter =
result.mode = mode result.mode = mode
result.sampleRate = sampleRate result.sampleRate = sampleRate
result.reset() result.reset()
result.a = 0.0 result.a = 0.0
result.b = 0.0 result.b = 0.0
result.maxCutoff = sampleRate / 6.0 result.maxCutoff = sampleRate / 6.0
result.needs_update = true result.needsUpdate = true
proc calcCoef*(self: var FilterSV) = proc calcCoef*(self: var SVFilter) =
if self.needs_update: if self.needsUpdate:
self.a = 2.0 * sin(PI * self.cutoff / self.sampleRate) self.a = 2.0 * sin(PI * self.cutoff / self.sampleRate)
if self.q > 0.0: if self.q > 0.0:
@ -42,36 +42,36 @@ proc calcCoef*(self: var FilterSV) =
else: else:
self.b = 0.0 self.b = 0.0
self.needs_update = false self.needsUpdate = false
proc setCutoff*(self: var FilterSV, cutoff: float) = proc setCutoff*(self: var SVFilter, cutoff: float) =
let fc = min(self.maxCutoff, cutoff) let fc = min(self.maxCutoff, cutoff)
if fc != self.cutoff: if fc != self.cutoff:
self.cutoff = fc self.cutoff = fc
self.needs_update = true self.needsUpdate = true
proc setQ*(self: var FilterSV, q: float) = proc setQ*(self: var SVFilter, q: float) =
if q != self.q: if q != self.q:
self.q = q self.q = q
self.needs_update = true self.needsUpdate = true
proc setMode*(self: var FilterSV, mode: FilterMode) = proc setMode*(self: var SVFilter, mode: FilterMode) =
self.mode = mode self.mode = mode
proc setSampleRate*(self: var FilterSV, sampleRate: float) = proc setSampleRate*(self: var SVFilter, sampleRate: float) =
if sampleRate != self.sampleRate: if sampleRate != self.sampleRate:
self.sampleRate = sampleRate self.sampleRate = sampleRate
self.needs_update = true self.needsUpdate = true
self.reset() self.reset()
self.calcCoef() self.calcCoef()
proc process*(self: var FilterSV, sample: float): float = proc process*(self: var SVFilter, sample: float): float =
self.lowPass += self.a * self.bandPass self.lowPass += self.a * self.bandPass
self.hiPass = sample - (self.lowPass + (self.b * self.bandPass)) self.hiPass = sample - (self.lowPass + (self.b * self.bandPass))
self.bandPass += self.a * self.hiPass self.bandPass += self.a * self.hiPass

View File

@ -0,0 +1,8 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:nymph:examples:tiltfilter>
a lv2:Plugin ;
lv2:binary <libtiltfilter.so> ;
rdfs:seeAlso <tiltfilter.ttl> .

View File

@ -0,0 +1,90 @@
@prefix bufs: <http://lv2plug.in/ns/ext/buf-size#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix params: <http://lv2plug.in/ns/ext/parameters#> .
@prefix props: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:nymph:examples:tiltfilter>
a lv2:Plugin , lv2:FilterPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
opts:supportedOption bufs:nominalBlockLength ,
bufs:maxBlockLength ,
params:sampleRate ;
lv2:port [
a lv2:InputPort, lv2:AudioPort ;
lv2:index 0 ;
lv2:name "Audio In" ;
lv2:symbol "in" ;
],
[
a lv2:OutputPort, lv2:AudioPort ;
lv2:index 1 ;
lv2:name "Audio Out" ;
lv2:symbol "out" ;
],
[
a lv2:InputPort, lv2:ControlPort ;
lv2:index 2 ;
lv2:name "Cutoff" ;
lv2:symbol "cutoff" ;
lv2:default 10000.0 ;
lv2:minimum 20.0 ;
lv2:maximum 20000.0 ;
units:unit units:hz ;
],
[
a lv2:InputPort, lv2:ControlPort ;
lv2:index 3 ;
lv2:name "Steepness" ;
lv2:symbol "steepness" ;
lv2:default 1.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0 ;
],
[
a lv2:InputPort, lv2:ControlPort ;
lv2:index 4 ;
lv2:name "Filter mode" ;
lv2:symbol "mode" ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 2 ;
lv2:portProperty lv2:enumeration, lv2:integer ;
lv2:scalePoint [
rdfs:label "Lowpass" ;
rdf:value 0 ;
],
[
rdfs:label "Highpass" ;
rdf:value 1 ;
],
[
rdfs:label "Bandpass" ;
rdf:value 2;
] ;
];
rdfs:comment """
A tilt EQ audio filter.
""" ;
doap:name "nymph tilt filter" ;
doap:license <https://spdx.org/licenses/MIT> ;
doap:maintainer [
foaf:name "Christopher Arndt" ;
foaf:mbox <mailto:info@chrisarndt.de> ;
foaf:homepage <https://gitlab.com/SpotlightKid/nymph> ;
] ;
lv2:microVersion 0 ;
lv2:minorVersion 1 .

165
examples/tiltfilter.nim Normal file
View File

@ -0,0 +1,165 @@
##
## Tilt Filter
##
## Nim translation of this Rust implementation:
##
## https://github.com/ardura/Actuate/blob/main/src/fx/ArduraFilter.rs
##
## Inspired by https://www.musicdsp.org/en/latest/Filters/267-simple-tilt-equalizer.html
## Lowpass, Bandpass, Highpass based off tilt filter code
import math
const
slopeNeg = -60.0
amp = 6.0 / ln(2.0)
denorm = pow(10.0, -30.0)
minFreq = 20.0
maxFreq = 20_000.0
type
FilterMode* = enum
fmLowPass, fmBandPass, fmHighPass
TiltFilter* = object
# Filter parameters
sampleRate: float64
centerFreq: float
steepness: float
mode: FilterMode
# Filter tracking / internal
needsUpdate: bool
sampleRateX3: float64
lowGain: float
highGain: float
a: float
b: float
lpOut: float
# Band pass separate vars
bandALow: float
bandBLow: float
bandOutLow: float
bandAHigh: float
bandBHigh: float
bandOutHigh: float
# Super useful function to scale an sample 0-1 into other ranges
proc scaleRange(sample, minOutput, maxOutput: float): float =
result = clamp(sample * (maxOutput - minOutput) + minOutput, minOutput, maxOutput)
proc initTiltFilter*(centerFreq, steepness: float, mode: FilterMode = fmLowPass, sampleRate: float64 = 48_000.0): TiltFilter =
let sampleRateX3 = 3.0 * sampleRate
case mode:
# These are the gains for the slopes when math happens later
of fmLowPass:
result.lowGain = exp(0.0 / amp) - 1.0
result.highGain = exp(slopeNeg / amp) - 1.0
of fmBandPass:
result.lowGain = exp(0.0 / amp) - 1.0
result.highGain = exp(slopeNeg / amp) - 1.0
of fmHighPass:
result.lowGain = exp(slopeNeg / amp) - 1.0
result.highGain = exp(0.0 / amp) - 1.0
let omega = 2.0 * PI * centerFreq
let n = 1.0 / (scaleRange(steepness, 0.98, 1.2) * (sampleRateX3 + omega))
result.a = 2.0 * omega * n
result.bandALow = result.a
result.bandAHigh = result.a
result.b = (sampleRateX3 - omega) * n
result.bandBLow = result.b
result.bandBHigh = result.b
result.lpOut = 0.0
result.bandOutLow = 0.0
result.bandOutHigh = 0.0
result.centerFreq = centerFreq
result.sampleRateX3 = sampleRateX3
result.steepness = steepness
result.sampleRate = sampleRate
result.mode = mode
result.needsUpdate = true
proc setMode*(self: var TiltFilter, mode: FilterMode) =
if mode != self.mode:
self.mode = mode
self.needsUpdate = true
proc setCenterFreq*(self: var TiltFilter, value: float) =
let freq = value.clamp(minFreq, maxFreq)
if freq != self.centerFreq:
self.centerFreq = freq
self.needsUpdate = true
proc setSteepness*(self: var TiltFilter, value: float) =
let steepness = value.clamp(0.0, 1.0)
if steepness != self.steepness:
self.steepness = steepness
self.needsUpdate = true
proc setSampleRate*(self: var TiltFilter, sampleRate: float) =
if sampleRate != self.sampleRate:
self.sampleRate = sampleRate
self.sampleRateX3 = self.sampleRate * 3.0
self.needsUpdate = true
proc reset*(self: var TiltFilter) =
discard
proc update*(self: var TiltFilter) =
if self.needsUpdate:
case self.mode:
of fmLowPass:
let omega = 2.0 * PI * self.centerFreq
let n = 1.0 / (scaleRange(self.steepness, 0.98, 1.2) * (self.sample_rate_x3 + omega))
self.b = (self.sampleRateX3 - omega) * n
self.lowGain = exp(0.0 / amp) - 1.0
self.highGain = exp(slopeNeg / amp) - 1.0
of fmBandPass:
let width = self.steepness * self.steepness * 500.0
let lowOmega = 2.0 * PI * (self.centerFreq - width).clamp(20.0, 16_000.0)
let lowN = 1.0 / (scaleRange(self.steepness, 0.98, 1.2) * (self.sampleRateX3 + lowOmega))
self.bandALow = 2.0 * lowOmega * lowN
self.bandBLow = (self.sampleRateX3 - lowOmega) * lowN
let highOmega = 2.0 * PI * (self.centerFreq + width).clamp(20.0, 16_000.0);
let highN = 1.0 / (scaleRange(self.steepness, 0.98, 1.2) * (self.sampleRateX3 + highOmega))
self.bandAHigh = 2.0 * highOmega * highN
self.bandBHigh = (self.sampleRateX3 - highOmega) * highN
self.lowGain = exp(0.0 / amp) - 1.0
self.highGain = exp(slopeNeg / amp) - 1.0
of fmHighPass:
let omega = 2.0 * PI * self.centerFreq
let n = 1.0 / (scaleRange(self.steepness, 0.98, 1.2) * (self.sampleRateX3 + omega))
self.a = 2.0 * omega * n
self.b = (self.sampleRateX3 - omega) * n
self.lowGain = exp(slopeNeg / amp) - 1.0
self.highGain = exp(0.0 / amp) - 1.0
self.needsUpdate = false
##
## Process the input sample using the tilt filter
##
proc process*(self: var TiltFilter, sample: float): float =
if self.mode == fmBandPass:
self.bandOutLow = self.bandALow * sample + self.bandBLow * self.bandOutLow
let temp = sample + self.highGain * self.bandOutLow + self.lowGain * (sample - self.bandOutLow)
self.bandOutHigh = self.bandAHigh * temp + self.bandBHigh * self.bandOutHigh
result = temp + self.lowGain * self.bandOutHigh + self.highGain * (temp - self.bandOutHigh) + denorm
else:
self.lpOut = self.a * sample + self.b * self.lpOut;
result = sample + self.lowGain * self.lpOut + self.highGain * (sample - self.lpOut) + denorm

View File

@ -0,0 +1,104 @@
## A tilt EQ / filter LV2 plugin
import nymph
import paramsmooth
import tiltfilter
const
PluginUri = "urn:nymph:examples:tiltfilter"
minFreq = 20.0
maxFreq = 20_000.0
type
SampleBuffer = UncheckedArray[cfloat]
PluginPort {.pure.} = enum
Input, Output, Frequency, Steepness, Mode
TiltFilterPlugin = object
input: ptr SampleBuffer
output: ptr SampleBuffer
freq: ptr cfloat
steepness: ptr cfloat
mode: ptr cfloat
flt: TiltFilter
smoothFreq: ParamSmooth
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
Lv2Handle {.cdecl.} =
try:
let plug = createShared(TiltFilterPlugin)
plug.flt = initTiltFilter(10_000.0, 1.0, fmLowPass, sampleRate)
plug.smoothFreq = initParamSmooth(20.0, sampleRate)
return cast[Lv2Handle](plug)
except OutOfMemDefect:
return nil
proc connectPort(instance: Lv2Handle; port: cuint;
dataLocation: pointer) {.cdecl.} =
let plug = cast[ptr TiltFilterPlugin](instance)
case cast[PluginPort](port)
of PluginPort.Input:
plug.input = cast[ptr SampleBuffer](dataLocation)
of PluginPort.Output:
plug.output = cast[ptr SampleBuffer](dataLocation)
of PluginPort.Frequency:
plug.freq = cast[ptr cfloat](dataLocation)
of PluginPort.Steepness:
plug.steepness = cast[ptr cfloat](dataLocation)
of PluginPort.Mode:
plug.mode = cast[ptr cfloat](dataLocation)
proc activate(instance: Lv2Handle) {.cdecl.} =
let plug = cast[ptr TiltFilterPlugin](instance)
plug.flt.reset()
proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} =
let plug = cast[ptr TiltFilterPlugin](instance)
let freq = plug.freq[].clamp(minFreq, maxFreq)
plug.flt.setMode(plug.mode[].clamp(0.0, 2.0).FilterMode)
plug.flt.setSteepness(plug.steepness[])
for pos in 0 ..< nSamples:
plug.flt.setCenterFreq(plug.smoothFreq.process(freq))
plug.flt.update()
plug.output[pos] = plug.flt.process(plug.input[pos])
proc deactivate(instance: Lv2Handle) {.cdecl.} =
discard
proc cleanup(instance: Lv2Handle) {.cdecl.} =
freeShared(cast[ptr TiltFilterPlugin](instance))
proc extensionData(uri: cstring): pointer {.cdecl.} =
return nil
proc NimMain() {.cdecl, importc.}
proc lv2Descriptor(index: cuint): ptr Lv2Descriptor {.
cdecl, exportc, dynlib, extern: "lv2_descriptor".} =
NimMain()
if index == 0:
result = createShared(Lv2Descriptor)
result.uri = cstring(PluginUri)
result.instantiate = instantiate
result.connectPort = connectPort
result.activate = activate
result.run = run
result.deactivate = deactivate
result.cleanup = cleanup
result.extensionData = extensionData

View File

@ -25,7 +25,10 @@ type Example = tuple
const examples = to_table({ const examples = to_table({
"amp": "urn:nymph:examples:amp", "amp": "urn:nymph:examples:amp",
"multimode_filter": "urn:nymph:examples:multimode-filter", "faustlpf": "urn:nymph:examples:faustlpf",
"miditranspose": "urn:nymph:examples:miditranspose",
"multimodefilter": "urn:nymph:examples:multimodefilter",
"tiltfilter": "urn:nymph:examples:tiltfilter",
}) })
@ -47,7 +50,7 @@ proc getExample(task_name: string): Example =
result.name = changeFileExt(args[^1], "") result.name = changeFileExt(args[^1], "")
let examplesDir = thisDir() / "examples" let examplesDir = thisDir() / "examples"
result.source = examplesDir / changeFileExt(result.name, "nim") result.source = examplesDir / result.name & "_plugin.nim"
if not fileExists(result.source): if not fileExists(result.source):
quit(&"Example '{result.name}' not found.") quit(&"Example '{result.name}' not found.")
@ -69,6 +72,9 @@ task build_ex, "Build given example plugin":
switch("mm", "arc") switch("mm", "arc")
switch("out", ex.dll) switch("out", ex.dll)
when defined(gcc):
switch("passC", "-fvisibility=hidden")
when not defined(release) and not defined(debug): when not defined(release) and not defined(debug):
echo &"Compiling plugin {ex.name} in release mode." echo &"Compiling plugin {ex.name} in release mode."
switch("define", "release") switch("define", "release")
@ -83,7 +89,7 @@ task lv2lint, "Run lv2lint check on given example plugin":
let ex = getExample("lv2lint") let ex = getExample("lv2lint")
if fileExists(ex.dll): if fileExists(ex.dll):
exec(&"lv2lint -s NimMain -I \"{ex.bundle}\" \"{ex.uri}\"") exec(&"lv2lint -s NimMain -s NimDestroyGlobals -I \"{ex.bundle}\" \"{ex.uri}\"")
else: else:
echo &"Example '{ex.name}' shared library not found. Use task 'build_ex' to build it." echo &"Example '{ex.name}' shared library not found. Use task 'build_ex' to build it."
@ -112,4 +118,3 @@ task lv2bm, "Run lv2bm benchmark on given example plugin":
exec(&"lv2bm --full-test -i white \"{ex.uri}\"") exec(&"lv2bm --full-test -i white \"{ex.uri}\"")
else: else:
echo &"Example '{ex.name}' shared library not found. Use task 'build_ex' to build it." echo &"Example '{ex.name}' shared library not found. Use task 'build_ex' to build it."

View File

@ -1,32 +1,2 @@
const LV2_CORE_URI* = "http://lv2plug.in/ns/lv2core" import nymph/core
export core
type Lv2Handle* = pointer
type Lv2Feature* = object
uri*: cstring
data*: pointer
type Lv2Descriptor* = object
uri*: cstring
instantiate*: proc(descriptor: ptr Lv2Descriptor, sampleRate: cdouble, bundlePath: cstring,
features: ptr ptr Lv2Feature): Lv2Handle {.cdecl.}
connectPort*: proc(instance: Lv2Handle, port: cuint, dataLocation: pointer) {.cdecl.}
activate*: proc(instance: Lv2Handle) {.cdecl.}
run*: proc(instance: Lv2Handle, sampleCount: cuint) {.cdecl.}
deactivate*: proc(instance: Lv2Handle) {.cdecl.}
cleanup*: proc(instance: Lv2Handle) {.cdecl.}
extensionData*: proc(uri: cstring): pointer {.cdecl.}
type lv2Descriptor* = proc(index: cuint): ptr Lv2Descriptor {.cdecl.}

188
src/nymph/atom.nim Normal file
View File

@ -0,0 +1,188 @@
## Copyright 2008-2016 David Robillard <d@drobilla.net>
## SPDX-License-Identifier: ISC
##
## A generic value container and several data types.
##
## See <http://lv2plug.in/ns/ext/atom> for details.
##
import ptrmath
import urid
const
lv2AtomBaseUri* ="http://lv2plug.in/ns/ext/atom"
lv2AtomPrefix = lv2AtomBaseUri & "#"
lv2AtomAtom* = lv2AtomPrefix & "Atom"
lv2AtomAtomPort* = lv2AtomPrefix & "AtomPort"
lv2AtomBlank* = lv2AtomPrefix & "Blank"
lv2AtomBool* = lv2AtomPrefix & "Bool"
lv2AtomChunk* = lv2AtomPrefix & "Chunk"
lv2AtomDouble* = lv2AtomPrefix & "Double"
lv2AtomEvent* = lv2AtomPrefix & "Event"
lv2AtomFloat* = lv2AtomPrefix & "Float"
lv2AtomInt* = lv2AtomPrefix & "Int"
lv2AtomLiteral* = lv2AtomPrefix & "Literal"
lv2AtomLong* = lv2AtomPrefix & "Long"
lv2AtomNumber* = lv2AtomPrefix & "Number"
lv2AtomObject* = lv2AtomPrefix & "Object"
lv2AtomPath* = lv2AtomPrefix & "Path"
lv2AtomProperty* = lv2AtomPrefix & "Property"
lv2AtomResource* = lv2AtomPrefix & "Resource"
lv2AtomSequence* = lv2AtomPrefix & "Sequence"
lv2AtomSound* = lv2AtomPrefix & "Sound"
lv2AtomString* = lv2AtomPrefix & "String"
lv2AtomTuple* = lv2AtomPrefix & "Tuple"
lv2AtomUri* = lv2AtomPrefix & "URI"
lv2AtomUrid* = lv2AtomPrefix & "URID"
lv2AtomVector* = lv2AtomPrefix & "Vector"
lv2AtomAtomtransfer* = lv2AtomPrefix & "atomTransfer"
lv2AtomBeattime* = lv2AtomPrefix & "beatTime"
lv2AtomBuffer* = lv2AtomPrefix & "bufferType"
lv2AtomChild* = lv2AtomPrefix & "childType"
lv2AtomEventTransfer* = lv2AtomPrefix & "eventTransfer"
lv2AtomFrameTime* = lv2AtomPrefix & "frameTime"
lv2AtomSupports* = lv2AtomPrefix & "supports"
lv2AtomTimeUnit* = lv2AtomPrefix & "timeUnit"
##
## Return a pointer to the contents of an Atom. The "contents" of an atom
## is the data past the complete type-specific header.
## @param type The type of the atom, for example AtomString.
## @param atom A variable-sized atom.
##
template atomContents*(`type`, atom: untyped): pointer =
cast[ptr uint8](atom) + sizeof(`type`)
##
## Return a pointer to the body of an Atom. The "body" of an atom is the
## data just past the Atom head (i.e. the same offset for all types).
##
template atomBody*(atom: untyped): pointer =
atomContents(Atom, atom)
type
## The header of an atom:Atom.
Atom* {.bycopy.} = object
size*: uint32 ## Size in bytes, not including type and size.
`type`*: Urid ## Type of this atom (mapped URI).
## An atom:Int or atom:Bool. May be cast to Atom.
AtomInt* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: int32 ## Integer value.
## An atom:Long. May be cast to Atom.
AtomLong* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: int64 ## Integer value.
## An atom:Float. May be cast to Atom.
AtomFloat* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: cfloat ## Floating point value.
## An atom:Double. May be cast to Atom.
AtomDouble* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: cdouble ## Floating point value.
## An atom:Bool. May be cast to Atom.
AtomBool* = distinct AtomInt
## An atom:URID. May be cast to Atom.
AtomUrid* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: Urid ## Urid.
## An atom:String. May be cast to Atom.
AtomString* {.bycopy.} = object
atom*: Atom ## Atom header.
## Contents (a null-terminated UTF-8 string) follow here.
## The body of an atom:Literal.
AtomLiteralBody* {.bycopy.} = object
datatype*: Urid ## Datatype Urid.
lang*: Urid ## Language Urid.
## Contents (a null-terminated UTF-8 string) follow here.
## An atom:Literal. May be cast to Atom.
AtomLiteral* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: AtomLiteralBody ## Body.
## An atom:Tuple. May be cast to Atom.
AtomTuple* {.bycopy.} = object
atom*: Atom ## Atom header.
## Contents (a series of complete atoms) follow here.
## The body of an atom:Vector.
AtomVectorBody* {.bycopy.} = object
childSize*: uint32 ## The size of each element in the vector.
childType*: uint32 ## The of each element in the vector.
## Contents (a series of packed atom bodies) follow here.
## An atom:Vector. May be cast to Atom.
AtomVector* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: AtomVectorBody ## Body.
## The body of an atom:Property (typically in an atom:Object).
AtomPropertyBody* {.bycopy.} = object
key*: Urid ## Key (predicate) (mapped URI).
context*: Urid ## Context URID (may be, and generally is, 0).
value*: Atom ## Value atom header.
## Value atom body follows here.
## An atom:Property. May be cast to Atom.
AtomProperty* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: AtomPropertyBody ## Body.
## The body of an atom:Object. May be cast to Atom.
AtomObjectBody* {.bycopy.} = object
id*: Urid ## Urid, or 0 for blank.
otype*: Urid ## Urid (same as rdf:type, for fast dispatch).
## Contents (a series of property bodies) follow here.
## An atom:Object. May be cast to Atom.
AtomObject* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: AtomObjectBody ## Body.
## The header of an atom:Event. Note this is NOT an Atom.
AtomEventTime* {.bycopy, union.} = object
frames*: int64 ## Time in audio frames.
beats*: cdouble ## Time in beats.
AtomEvent* {.bycopy.} = object
time*: AtomEventTime ## Time stamp. Which is valid is determined by context.
body*: Atom ## Event body atom header.
## Body atom contents follow here.
##
## The body of an AtomSequence (a sequence of events).
##
## The unit field is either a Urid that describes an appropriate time stamp
## type, or may be 0 where a default stamp type is known. For
## lv2Descriptor.run(), the default stamp type is audio frames.
##
## The contents of a sequence is a series of AtomEvent, each aligned
## to 64-bits, for example:
##
## ```
## | Event 1 (size 6) | Event 2
## | | | | | | | | |
## | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
## |FRAMES |SIZE |TYPE |DATADATADATAPAD|FRAMES |...
## ```
##
AtomSequenceBody* {.bycopy.} = object
unit*: Urid ## Urid of unit of event time stamps.
pad*: uint32 ## Currently unused.
## Contents (a series of events) follow here.
## An atom:Sequence.
AtomSequence* {.bycopy.} = object
atom*: Atom ## Atom header.
body*: AtomSequenceBody ## Body.

199
src/nymph/atom/util.nim Normal file
View File

@ -0,0 +1,199 @@
## Copyright 2008-2015 David Robillard <d@drobilla.net>
## SPDX-License-Identifier: ISC
##
## Helper functions for the LV2 Atom extension.
##
## Utilities for working with atoms.
##
import ../atom
import ../ptrmath
import ../urid
##
## Pad a size to 64 bits.
##
proc atomPadSize*(size: uint32): uint32 {.inline.} =
let mask: uint32 = 7
return (size + mask) and not mask
##
## Return the total size of `atom`, including the header.
##
proc atomTotalSize*(atom: ptr Atom): uint32 {.inline.} =
return cast[uint32](sizeof(atom)) + atom.size
##
## Return true iff `atom` is null.
##
proc atomIsNull*(atom: ptr Atom): bool {.inline.} =
return atom.isNil or (atom.`type` == Urid(0) and atom.size == 0)
##
## Return true iff `a` is equal to `b`.
##
proc atomEquals*(a: ptr Atom; b: ptr Atom): bool {.inline.} =
return (a == b) or
((a.`type` == b.`type`) and (a.size == b.size) and
cmpMem(a + 1, b + 1, a.size) == 0)
##
## Sequence Iterator
##
## Get an iterator pointing to the first event in a Sequence body.
##
template atomSequenceBegin*(body: ptr AtomSequenceBody): ptr AtomEvent =
cast[ptr AtomEvent](body + 1)
##
## Get an iterator pointing to the end of a Sequence body.
##
template atomSequenceEnd*(body: ptr AtomSequenceBody; size: Natural): ptr AtomEvent =
cast[ptr AtomEvent](cast[ptr uint8](body) + atomPadSize(size.uint32))
##
## Return true iff `i` has reached the end of `body`.
##
template atomSequenceIsEnd*(body: ptr AtomSequenceBody; size: Natural; i: ptr AtomEvent): bool =
cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint)
##
## Return an iterator to the element following `i`.
##
template atomSequenceNext*(i: ptr AtomEvent): ptr AtomEvent =
cast[ptr AtomEvent](cast[ptr uint8](i) + sizeof(AtomEvent) + atomPadSize(i.body.size))
##
## An iterator for looping over all events in an AtomSequence.
## @param seq Pointer to the sequence to iterate over
##
iterator items*(seq: ptr AtomSequence): ptr AtomEvent {.inline.} =
var event = atomSequenceBegin(seq.body.addr)
while not atomSequenceIsEnd(seq.body.addr, seq.atom.size, event):
yield event
event = atomSequenceNext(event)
##
## An iterator for looping over all events in an AtomSequenceBody.
## @param body Pointer to the sequence body to iterate over
## @param size Size in bytes of the sequence body (including the 8 bytes of AtomSequenceBody)
##
iterator items*(body: ptr AtomSequenceBody, size: Natural): ptr AtomEvent {.inline.} =
var event = atomSequenceBegin(body)
while not atomSequenceIsEnd(body, size, event):
yield event
event = atomSequenceNext(event)
##
## Sequence Utilities
##
##
## Test if AtomSequence is empty, i.e the body has no events
##
template atomSequenceIsEmpty*(seq: ptr AtomSequence): bool =
seq.atom.size == sizeof(AtomSequenceBody).uint32
##
## Clear all events from `sequence`.
##
## This simply resets the size field, the other fields are left untouched.
##
template atomSequenceClear*(seq: ptr AtomSequence) =
seq.atom.size = sizeof(AtomSequenceBody).uint32
##
## Append an event at the end of `sequence`.
##
## @param seq Sequence to append to.
## @param capacity Total capacity of the sequence atom
## (as set by the host for sequence output ports).
## @param event Event to write.
##
## @return A pointer to the newly written event in `seq`,
## or nil on failure (insufficient space).
##
proc atomSequenceAppendEvent*(seq: ptr AtomSequence; capacity: uint32;
event: ptr AtomEvent): ptr AtomEvent {.inline.} =
let totalSize = sizeof(AtomEvent).uint32 + event.body.size
if capacity - seq.atom.size < totalSize:
return nil
var e = atomSequenceEnd(seq.body.addr, seq.atom.size)
copyMem(e, event, totalSize)
seq.atom.size += atomPadSize(totalSize)
return e
##
## Tuple Iterator
##
## Get an iterator pointing to the first element in `tup`.
##
template atomTupleBegin*(tup: ptr AtomTuple): ptr Atom =
cast[ptr Atom](atomContents(Atom, tup))
##
## Return true iff `i` has reached the end of `body`.
##
template atomTupleIsEnd*(body: pointer; size: Natural; i: ptr Atom): bool =
cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint)
##
## Return an iterator to the element following `i`.
##
template atomTupleNext*(i: ptr Atom): ptr Atom =
cast[ptr Atom](cast[ptr uint8](i) + sizeof(Atom) + atomPadSize(i.size))
##
## An iterator for looping over all elements of an AtomTuple.
## @param tuple Pointer to the tuple to iterate over
##
iterator items*(tup: ptr AtomTuple): ptr Atom {.inline.} =
var atom = atomTupleBegin(tup)
while not atomTupleIsEnd(atomBody(tup), tup.atom.size, atom):
yield atom
atom = atomTupleNext(atom)
##
## An iterator for looping over all elements of a headerless tuple.
## @param tuple Pointer to the first Atom of the tuple to iterate over
## @param size Size in bytes of the tuple body
##
iterator items*(tup: ptr Atom, size: Natural): ptr Atom {.inline.} =
var atom = tup
while not atomTupleIsEnd(tup, size, atom):
yield atom
atom = atomTupleNext(atom)
##
## Object Iterator
##
## Return a pointer to the first property in `body`.
##
template atomObjectBegin*(body: ptr AtomObjectBody): ptr AtomPropertyBody =
cast[ptr AtomPropertyBody](body + 1)
##
## Return true iff `i` has reached the end of `obj`.
##
template atomObjectIsEnd*(body: ptr AtomObjectBody; size: Natural; i: ptr AtomPropertyBody): bool =
cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint)
##
## Return an iterator to the property following `i`.
##
template atomObjectNext*(i: ptr AtomPropertyBody): ptr AtomPropertyBody =
let value = cast[ptr Atom](cast[ptr uint8](i) + 2 * sizeof(uint32))
return cast[ptr AtomPropertyBody](cast[ptr uint8](i) + atomPadSize(sizeof(AtomPropertyBody).uint32 + value.size))
## TODO:
##
## A iterator for looping over all properties of an Object.
## @param obj The object to iterate over
##
## TODO:
##
## Like LV2_ATOM_OBJECT_FOREACH but for a headerless object body.
##

120
src/nymph/core.nim Normal file
View File

@ -0,0 +1,120 @@
const
lv2CoreBaseUri* = "http://lv2plug.in/ns/lv2core"
lv2CorePrefix = lv2CoreBaseUri & "#"
# Classes (http://lv2plug.in/ns/lv2core#ref-classes):
lv2CoreClassAllpassPlugin* = lv2CorePrefix & "AllpassPlugin"
lv2CoreClassAmplifierPlugin* = lv2CorePrefix & "AmplifierPlugin"
lv2CoreClassAnalyserPlugin* = lv2CorePrefix & "AnalyserPlugin"
lv2CoreClassAudioPort* = lv2CorePrefix & "AudioPort"
lv2CoreClassBandpassPlugin* = lv2CorePrefix & "BandpassPlugin"
lv2CoreClassCVPort* = lv2CorePrefix & "CVPort"
lv2CoreClassChorusPlugin* = lv2CorePrefix & "ChorusPlugin"
lv2CoreClassCombPlugin* = lv2CorePrefix & "CombPlugin"
lv2CoreClassCompressorPlugin* = lv2CorePrefix & "CompressorPlugin"
lv2CoreClassConstantPlugin* = lv2CorePrefix & "ConstantPlugin"
lv2CoreClassControlPort* = lv2CorePrefix & "ControlPort"
lv2CoreClassConverterPlugin* = lv2CorePrefix & "ConverterPlugin"
lv2CoreClassDelayPlugin* = lv2CorePrefix & "DelayPlugin"
lv2CoreClassDistortionPlugin* = lv2CorePrefix & "DistortionPlugin"
lv2CoreClassDynamicsPlugin* = lv2CorePrefix & "DynamicsPlugin"
lv2CoreClassEQPlugin* = lv2CorePrefix & "EQPlugin"
lv2CoreClassEnvelopePlugin* = lv2CorePrefix & "EnvelopePlugin"
lv2CoreClassExpanderPlugin* = lv2CorePrefix & "ExpanderPlugin"
lv2CoreClassExtensionData* = lv2CorePrefix & "ExtensionData"
lv2CoreClassFeature* = lv2CorePrefix & "Feature"
lv2CoreClassFilterPlugin* = lv2CorePrefix & "FilterPlugin"
lv2CoreClassFlangerPlugin* = lv2CorePrefix & "FlangerPlugin"
lv2CoreClassFunctionPlugin* = lv2CorePrefix & "FunctionPlugin"
lv2CoreClassGatePlugin* = lv2CorePrefix & "GatePlugin"
lv2CoreClassGeneratorPlugin* = lv2CorePrefix & "GeneratorPlugin"
lv2CoreClassHighpassPlugin* = lv2CorePrefix & "HighpassPlugin"
lv2CoreClassInputPort* = lv2CorePrefix & "InputPort"
lv2CoreClassInstrumentPlugin* = lv2CorePrefix & "InstrumentPlugin"
lv2CoreClassLimiterPlugin* = lv2CorePrefix & "LimiterPlugin"
lv2CoreClassLowpassPlugin* = lv2CorePrefix & "LowpassPlugin"
lv2CoreClassMixerPlugin* = lv2CorePrefix & "MixerPlugin"
lv2CoreClassModulatorPlugin* = lv2CorePrefix & "ModulatorPlugin"
lv2CoreClassMultiEQPlugin* = lv2CorePrefix & "MultiEQPlugin"
lv2CoreClassOscillatorPlugin* = lv2CorePrefix & "OscillatorPlugin"
lv2CoreClassOutputPort* = lv2CorePrefix & "OutputPort"
lv2CoreClassParaEQPlugin* = lv2CorePrefix & "ParaEQPlugin"
lv2CoreClassPhaserPlugin* = lv2CorePrefix & "PhaserPlugin"
lv2CoreClassPitchPlugin* = lv2CorePrefix & "PitchPlugin"
lv2CoreClassPlugin* = lv2CorePrefix & "Plugin"
lv2CoreClassPluginBase* = lv2CorePrefix & "PluginBase"
lv2CoreClassPoint* = lv2CorePrefix & "Point"
lv2CoreClassPort* = lv2CorePrefix & "Port"
lv2CoreClassPortProperty* = lv2CorePrefix & "Port"
lv2CoreClassResource* = lv2CorePrefix & "Resource"
lv2CoreClassReverbPlugin* = lv2CorePrefix & "ReverbPlugin"
lv2CoreClassScalePoint* = lv2CorePrefix & "ScalePoint"
lv2CoreClassSimulatorPlugin* = lv2CorePrefix & "SimulatorPlugin"
lv2CoreClassSpatialPlugin* = lv2CorePrefix & "SpatialPlugin"
lv2CoreClassSpecification* = lv2CorePrefix & "Specification"
lv2CoreClassSpectralPlugin* = lv2CorePrefix & "SpectralPlugin"
lv2CoreClassUtilityPlugin* = lv2CorePrefix & "UtilityPlugin"
lv2CoreClassWaveshaperPlugin* = lv2CorePrefix & "WaveshaperPlugin"
# Properties (http://lv2plug.in/ns/lv2core#ref-properties):
lv2CorePropertyAppliesTo* = lv2CorePrefix & "appliesTo"
lv2CorePropertyBinary* = lv2CorePrefix & "binary"
lv2CorePropertyDefault* = lv2CorePrefix & "default"
lv2CorePropertyDesignation* = lv2CorePrefix & "designation"
lv2CorePropertyDocumentation* = lv2CorePrefix & "documentation"
lv2CorePropertyExtensionData* = lv2CorePrefix & "extensionData"
lv2CorePropertyIndex* = lv2CorePrefix & "index"
lv2CorePropertyLatency* = lv2CorePrefix & "latency"
lv2CorePropertyMaximum* = lv2CorePrefix & "maximum"
lv2CorePropertyMicroVersion* = lv2CorePrefix & "microVersion"
lv2CorePropertyMinimum* = lv2CorePrefix & "minimum"
lv2CorePropertyMinorVersion* = lv2CorePrefix & "minorVersion"
lv2CorePropertyName* = lv2CorePrefix & "name"
lv2CorePropertyOptionalFeature* = lv2CorePrefix & "optionalFeature"
lv2CorePropertyPort* = lv2CorePrefix & "port"
lv2CorePropertyPortProperty* = lv2CorePrefix & "portProperty"
lv2CorePropertyProject* = lv2CorePrefix & "project"
lv2CorePropertyPrototype* = lv2CorePrefix & "prototype"
lv2CorePropertyReportsLatency* = lv2CorePrefix & "reportsLatency"
lv2CorePropertyRequiredFeature* = lv2CorePrefix & "requiredFeature"
lv2CorePropertyScalePoint* = lv2CorePrefix & "scalePoint"
lv2CorePropertySymbol* = lv2CorePrefix & "symbol"
# Instances (http://lv2plug.in/ns/lv2core#ref-instances)
lv2CoreInstanceConnectionOptional* = lv2CorePrefix & "connectionOptional"
lv2CoreInstanceControl* = lv2CorePrefix & "control"
lv2CoreInstanceEnumeration* = lv2CorePrefix & "enumeration"
lv2CoreInstanceFreeWheeling* = lv2CorePrefix & "freeWheeling"
lv2CoreInstanceHardRTCapable* = lv2CorePrefix & "hardRTCapable"
lv2CoreInstanceInPlaceBroken* = lv2CorePrefix & "inPlaceBroken"
lv2CoreInstanceInteger* = lv2CorePrefix & "integer"
lv2CoreInstanceIsLive* = lv2CorePrefix & "isLive"
lv2CoreInstanceSampleRate* = lv2CorePrefix & "sampleRate"
lv2CoreInstanceToggled* = lv2CorePrefix & "toggled"
type Lv2Handle* = pointer
type Lv2Feature* = object
uri*: cstring
data*: pointer
type Lv2Descriptor* = object
uri*: cstring
instantiate*: proc(descriptor: ptr Lv2Descriptor, sampleRate: cdouble, bundlePath: cstring,
features: ptr UncheckedArray[ptr Lv2Feature]): Lv2Handle {.cdecl.}
connectPort*: proc(instance: Lv2Handle, port: cuint, dataLocation: pointer) {.cdecl.}
activate*: proc(instance: Lv2Handle) {.cdecl.}
run*: proc(instance: Lv2Handle, sampleCount: cuint) {.cdecl.}
deactivate*: proc(instance: Lv2Handle) {.cdecl.}
cleanup*: proc(instance: Lv2Handle) {.cdecl.}
extensionData*: proc(uri: cstring): pointer {.cdecl.}
type lv2Descriptor* = proc(index: cuint): ptr Lv2Descriptor {.cdecl.}

189
src/nymph/midi.nim Normal file
View File

@ -0,0 +1,189 @@
## Copyright 2012-2016 David Robillard <d@drobilla.net>
## SPDX-License-Identifier: ISC
##
## Definitions of standard MIDI messages.
##
## See <http://lv2plug.in/ns/ext/midi> for details.
##
const
lv2MidiBaseUri = "http://lv2plug.in/ns/ext/midi"
lv2MidiPrefix = lv2MidiBaseUri & "#"
lv2MidiActiveSense* = lv2MidiPrefix & "ActiveSense"
lv2MidiAftertouch* = lv2MidiPrefix & "Aftertouch"
lv2MidiBender* = lv2MidiPrefix & "Bender"
lv2MidiChannelPressure* = lv2MidiPrefix & "ChannelPressure"
lv2MidiChunk* = lv2MidiPrefix & "Chunk"
lv2MidiClock* = lv2MidiPrefix & "Clock"
lv2MidiContinue* = lv2MidiPrefix & "Continue"
lv2MidiController* = lv2MidiPrefix & "Controller"
lv2MidiMidiEvent* = lv2MidiPrefix & "MidiEvent"
lv2MidiNoteOff* = lv2MidiPrefix & "NoteOff"
lv2MidiNoteOn* = lv2MidiPrefix & "NoteOn"
lv2MidiProgramChange* = lv2MidiPrefix & "ProgramChange"
lv2MidiQuarterFrame* = lv2MidiPrefix & "QuarterFrame"
lv2MidiReset* = lv2MidiPrefix & "Reset"
lv2MidiSongPosition* = lv2MidiPrefix & "SongPosition"
lv2MidiSongSelect* = lv2MidiPrefix & "SongSelect"
lv2MidiStart* = lv2MidiPrefix & "Start"
lv2MidiStop* = lv2MidiPrefix & "Stop"
lv2MidiSystemCommon* = lv2MidiPrefix & "SystemCommon"
lv2MidiSystemExclusive* = lv2MidiPrefix & "SystemExclusive"
lv2MidiSystemMessage* = lv2MidiPrefix & "SystemMessage"
lv2MidiSystemRealtime* = lv2MidiPrefix & "SystemRealtime"
lv2MidiTick* = lv2MidiPrefix & "Tick"
lv2MidiTuneRequest* = lv2MidiPrefix & "TuneRequest"
lv2MidiVoiceMessage* = lv2MidiPrefix & "VoiceMessage"
lv2MidiPropBenderValue* = lv2MidiPrefix & "benderValue"
lv2MidiPropBinding* = lv2MidiPrefix & "binding"
lv2MidiPropByteNumber* = lv2MidiPrefix & "byteNumber"
lv2MidiPropChannel* = lv2MidiPrefix & "channel"
lv2MidiPropChunk* = lv2MidiPrefix & "chunk"
lv2MidiPropControllerNumber* = lv2MidiPrefix & "controllerNumber"
lv2MidiPropControllerValue* = lv2MidiPrefix & "controllerValue"
lv2MidiPropNoteNumber* = lv2MidiPrefix & "noteNumber"
lv2MidiPropPressure* = lv2MidiPrefix & "pressure"
lv2MidiPropProgramNumber* = lv2MidiPrefix & "programNumber"
lv2MidiPropProperty* = lv2MidiPrefix & "property"
lv2MidiPropSongNumber* = lv2MidiPrefix & "songNumber"
lv2MidiPropSongPosition* = lv2MidiPrefix & "songPosition"
lv2MidiPropStatus* = lv2MidiPrefix & "status"
lv2MidiPropStatusMask* = lv2MidiPrefix & "statusMask"
lv2MidiPropVelocity* = lv2MidiPrefix & "velocity"
##
## MIDI Message Type
##
## This includes both voice messages (which have a channel) and system messages
## (which do not), as well as a sentinel value for invalid messages. To get
## the type of a message suitable for use in a switch statement, use
## midiGetMessageType() on the status byte.
##
type
MidiMessageType* = enum
midiMsgInvalid = 0, # Invalid Message
midiMsgNoteOff = 0x80, # Note Off
midiMsgNoteOn = 0x90, # Note On
midiMsgNotePressure = 0xA0, # Note Pressure
midiMsgController = 0xB0, # Controller
midiMsgPgmChange = 0xC0, # Program Change
midiMsgChannelPressure = 0xD0, # Channel Pressure
midiMsgBender = 0xE0, # Pitch Bender
midiMsgSystemExclusive = 0xF0, # System Exclusive Begin
midiMsgMtcQuarter = 0xF1, # MTC Quarter Frame
midiMsgSongPos = 0xF2, # Song Position
midiMsgSongSelect = 0xF3, # Song Select
midiMsgTuneRequest = 0xF6, # Tune Request
midiMsgClock = 0xF8, # Clock
midiMsgStart = 0xFA, # Start
midiMsgContinue = 0xFB, # Continue
midiMsgStop = 0xFC, # Stop
midiMsgActiveSense = 0xFE, # Active Sensing
midiMsgReset = 0xFF # Reset
##
## Standard MIDI Controller Numbers
##
type
MidiController* = enum
midiCtlMsbBank = 0x00, ## Bank Selection
midiCtlMsbModwheel = 0x01, # Modulation
midiCtlMsbBreath = 0x02, # Breath
midiCtlMsbFoot = 0x04, # Foot
midiCtlMsbPortamentoTime = 0x05, # Portamento Time
midiCtlMsbDataEntry = 0x06, # Data Entry
midiCtlMsbMainVolume = 0x07, # Main Volume
midiCtlMsbBalance = 0x08, # Balance
midiCtlMsbPan = 0x0A, # Panpot
midiCtlMsbExpression = 0x0B, # Expression
midiCtlMsbEffect1 = 0x0C, # Effect1
midiCtlMsbEffect2 = 0x0D, # Effect2
midiCtlMsbGeneralPurpose1 = 0x10, # General Purpose 1
midiCtlMsbGeneralPurpose2 = 0x11, # General Purpose 2
midiCtlMsbGeneralPurpose3 = 0x12, # General Purpose 3
midiCtlMsbGeneralPurpose4 = 0x13, # General Purpose 4
midiCtlLsbBank = 0x20, # Bank Selection
midiCtlLsbModwheel = 0x21, # Modulation
midiCtlLsbBreath = 0x22, # Breath
midiCtlLsbFoot = 0x24, # Foot
midiCtlLsbPortamentoTime = 0x25, # Portamento Time
midiCtlLsbDataEntry = 0x26, # Data Entry
midiCtlLsbMainVolume = 0x27, # Main Volume
midiCtlLsbBalance = 0x28, # Balance
midiCtlLsbPan = 0x2A, # Panpot
midiCtlLsbExpression = 0x2B, # Expression
midiCtlLsbEffect1 = 0x2C, # Effect1
midiCtlLsbEffect2 = 0x2D, # Effect2
midiCtlLsbGeneralPurpose1 = 0x30, # General Purpose 1
midiCtlLsbGeneralPurpose2 = 0x31, # General Purpose 2
midiCtlLsbGeneralPurpose3 = 0x32, # General Purpose 3
midiCtlLsbGeneralPurpose4 = 0x33, # General Purpose 4
midiCtlSustain = 0x40, # Sustain Pedal
midiCtlPortamento = 0x41, # Portamento
midiCtlSostenuto = 0x42, # Sostenuto
midiCtlSoftPedal = 0x43, # Soft Pedal
midiCtlLegatoFootswitch = 0x44, # Legato Foot Switch
midiCtlHold2 = 0x45, # Hold2
midiCtlSc1SoundVariation = 0x46, # SC1 Sound Variation
midiCtlSc2Timbre = 0x47, # SC2 Timbre
midiCtlSc3ReleaseTime = 0x48, # SC3 Release Time
midiCtlSc4AttackTime = 0x49, # SC4 Attack Time
midiCtlSc5Brightness = 0x4A, # SC5 Brightness
midiCtlSc6 = 0x4B, # SC6
midiCtlSc7 = 0x4C, # SC7
midiCtlSc8 = 0x4D, # SC8
midiCtlSc9 = 0x4E, # SC9
midiCtlSc10 = 0x4F, # SC10
midiCtlGeneralPurpose5 = 0x50, # General Purpose 5
midiCtlGeneralPurpose6 = 0x51, # General Purpose 6
midiCtlGeneralPurpose7 = 0x52, # General Purpose 7
midiCtlGeneralPurpose8 = 0x53, # General Purpose 8
midiCtlPortamentoControl = 0x54, # Portamento Control
midiCtlE1ReverbDepth = 0x5B, # E1 Reverb Depth
midiCtlE2TremoloDepth = 0x5C, # E2 Tremolo Depth
midiCtlE3ChorusDepth = 0x5D, # E3 Chorus Depth
midiCtlE4DetuneDepth = 0x5E, # E4 Detune Depth
midiCtlE5PhaserDepth = 0x5F, # E5 Phaser Depth
midiCtlDataIncrement = 0x60, # Data Increment
midiCtlDataDecrement = 0x61, # Data Decrement
midiCtlNrpnLsb = 0x62, # Non-registered Parameter Number
midiCtlNrpnMsb = 0x63, # Non-registered Parameter Number
midiCtlRpnLsb = 0x64, # Registered Parameter Number
midiCtlRpnMsb = 0x65, # Registered Parameter Number
midiCtlAllSoundsOff = 0x78, # All Sounds Off
midiCtlResetControllers = 0x79, # Reset Controllers
midiCtlLocalControlSwitch = 0x7A, # Local Control Switch
midiCtlAllNotesOff = 0x7B, # All Notes Off
midiCtlOmniOff = 0x7C, # Omni Off
midiCtlOmniOn = 0x7D, # Omni On
midiCtlMono1 = 0x7E, # Mono1
midiCtlMono2 = 0x7F # Mono2
##
## Return true iff `msg` is a MIDI voice message (which has a channel).
##
proc midiIsVoiceMessage*(msg: UncheckedArray[uint8]): bool {.inline.} =
return msg[0] >= 0x80 and msg[0] < 0xF0
##
## Return true iff `msg` is a MIDI system message (which has no channel).
##
proc midiIsSystemMessage*(msg: UncheckedArray[uint8]): bool {.inline.} =
case msg[0]
of 0xF4, 0xF5, 0xF7, 0xF9, 0xFD:
return false
else:
return (msg[0] and 0xF0) == 0xF0
##
## Return the type of a MIDI message.
##
## @param msg Pointer to the start (status byte) of a MIDI message.
##
proc midiGetMessageType*(msg: UncheckedArray[uint8]): MidiMessageType {.inline.} =
if midiIsVoiceMessage(msg):
return cast[MidiMessageType](msg[0] and 0xF0)
if midiIsSystemMessage(msg):
return cast[MidiMessageType](msg[0])
return midiMsgInvalid

8
src/nymph/plugin.nim Normal file
View File

@ -0,0 +1,8 @@
plugin:
urid: urn:foobar#v1
port_group:
name: "stereo"
audio_input:
"in_l"
audio_input:
"in_r"

6
src/nymph/ptrmath.nim Normal file
View File

@ -0,0 +1,6 @@
##
## Increment pointer `p` by `offset` that jumps memory in increments of
## the size of `T`.
##
proc `+`*[T](p: ptr T, offset: SomeInteger): ptr T =
return cast[ptr T](cast[int](p) +% (offset.int * sizeof(T)))

95
src/nymph/urid.nim Normal file
View File

@ -0,0 +1,95 @@
## Copyright 2008-2016 David Robillard <d@drobilla.net>
## Copyright 2011 Gabriel M. Beddingfield <gabrbedd@gmail.com>
## SPDX-License-Identifier: ISC
##
## Features for mapping URIs to and from integers.
##
## See <http://lv2plug.in/ns/ext/urid> for details.
##
const
lv2UridBaseUri* = "http://lv2plug.in/ns/ext/urid"
lv2UridPrefix* = lv2UridBaseUri & "#"
lv2UridMap* = lv2UridPrefix & "map"
lv2UridUnmap* = lv2UridPrefix & "unmap"
type
##
## URI mapped to an integer.
##
Urid* = distinct uint32
##
## Opaque pointer to host data for UridMap.
##
UridMapHandle* = pointer
##
## Opaque pointer to host data for uridUnmap.
##
UridUnmapHandle* = pointer
##
## URID Map Feature (lv2UridMap)
##
UridMap* {.bycopy.} = object
##
## Opaque pointer to host data.
##
## This MUST be passed to map_uri() whenever it is called.
## Otherwise, it must not be interpreted in any way.
##
handle*: UridMapHandle
##
## Get the numeric ID of a URI.
##
## If the ID does not already exist, it will be created.
##
## This function is referentially transparent; any number of calls with the
## same arguments is guaranteed to return the same value over the life of a
## plugin instance. Note, however, that several URIs MAY resolve to the
## same ID if the host considers those URIs equivalent.
##
## This function is not necessarily very fast or RT-safe: plugins SHOULD
## cache any IDs they might need in performance critical situations.
##
## The return value 0 is reserved and indicates that an ID for that URI
## could not be created for whatever reason. However, hosts SHOULD NOT
## return 0 from this function in non-exceptional circumstances (i.e. the
## URI map SHOULD be dynamic).
##
## @param handle Must be the callback_data member of this struct.
## @param uri The URI to be mapped to an integer ID.
##
map*: proc (handle: UridMapHandle; uri: cstring): Urid
##
## URI Unmap Feature (lv2UridUnmap)
##
UridUnmap* {.bycopy.} = object
##
## Opaque pointer to host data.
##
## This MUST be passed to unmap() whenever it is called.
## Otherwise, it must not be interpreted in any way.
##
handle*: UridUnmapHandle
##
## Get the URI for a previously mapped numeric ID.
##
## Returns NULL if `urid` is not yet mapped. Otherwise, the corresponding
## URI is returned in a canonical form. This MAY not be the exact same
## string that was originally passed to LV2_URID_Map::map(), but it MUST be
## an identical URI according to the URI syntax specification (RFC3986). A
## non-NULL return for a given `urid` will always be the same for the life
## of the plugin. Plugins that intend to perform string comparison on
## unmapped URIs SHOULD first canonicalise URI strings with a call to
## map_uri() followed by a call to unmap_uri().
##
## @param handle Must be the callback_data member of this struct.
## @param urid The ID to be mapped back to the URI string.
##
unmap*: proc (handle: UridUnmapHandle; urid: Urid): cstring
proc `==`* (x: Urid, y: Urid): bool {.borrow.}

42
src/nymph/util.nim Normal file
View File

@ -0,0 +1,42 @@
import core
## Return the data for a feature in a features array.
## If the feature is not found, nil is returned. Note that this function is
## only useful for features with data, and can not detect features that are
## present but have nil data.
proc lv2FeaturesData*(features: ptr UncheckedArray[ptr Lv2Feature], uri: string): pointer =
if not features.isNil:
var i = 0
while true:
let feature = features[i]
if feature.isNil:
break
if feature[].uri == uri.cstring:
return feature[].data
inc i
return nil
## Query a features array.
##
## This function allows getting several features in one call, and detect
## missing required features, with the same caveat of lv2_features_data().
##
## The arguments should be a series of const char* uri, void** data, bool
## required, terminated by a NULL URI. The data pointers MUST be initialized
## to NULL. For example:
##
## LV2_URID_Log* log = NULL;
## LV2_URID_Map* map = NULL;
## const char* missing = lv2_features_query(
## features,
## LV2_LOG__log, &log, false,
## LV2_URID__map, &map, true,
## NULL);
##
##
## @return NULL on success, otherwise the URI of this missing feature.