Compare commits
25 Commits
feat/dynsm
...
master
Author | SHA1 | Date |
---|---|---|
Christopher Arndt | 9474b4481e | |
Christopher Arndt | b37954a27b | |
Christopher Arndt | 9b33bf10bf | |
Christopher Arndt | aa3bae40fb | |
Christopher Arndt | 0a7c8c1730 | |
Christopher Arndt | 38dc236740 | |
Christopher Arndt | e6c66ed72c | |
Christopher Arndt | f40df2491e | |
Christopher Arndt | 4dcfd1abdb | |
Christopher Arndt | 1f72523055 | |
Christopher Arndt | 521fefc531 | |
Christopher Arndt | eb814d16c3 | |
Christopher Arndt | f001184fea | |
Christopher Arndt | 75f3ce03b3 | |
Christopher Arndt | 4d60684b07 | |
Christopher Arndt | f71ec0d0df | |
Christopher Arndt | b88f75efce | |
Christopher Arndt | af27abbaca | |
Christopher Arndt | 1dcf5ac82f | |
Christopher Arndt | 11c9fc653d | |
Christopher Arndt | d39cea5a98 | |
Christopher Arndt | 9bb9321dcb | |
Christopher Arndt | ba6aaa22ab | |
Christopher Arndt | ff6ddf77d2 | |
Christopher Arndt | 095774213d |
|
@ -2,3 +2,4 @@ nimble.paths
|
|||
*.so
|
||||
*.dll
|
||||
/.lv2/
|
||||
*.code-workspace
|
||||
|
|
95
README.md
95
README.md
|
@ -1,6 +1,7 @@
|
|||
# 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
|
||||
|
@ -22,53 +23,68 @@ Install the `amp.lv2` example plugin:
|
|||
cp -a examples/amp.lv2 ~/.lv2
|
||||
|
||||
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
|
||||
to the basename of the plugin's LV2 bundle dir.
|
||||
can be built and tested with similar commands. Just change the example name to
|
||||
the basename of the plugin's LV2 bundle dir.
|
||||
|
||||
Currently, there is only one other example plugin, `multimode_filter`, but
|
||||
more will be added soon.
|
||||
Currently, there are just a few other example plugins:
|
||||
|
||||
* [`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
|
||||
|
||||
* 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
|
||||
them as you like (also rename `amp.lv2/amp.ttl`). I'll use `myplugin` as the
|
||||
base name in the examples below.
|
||||
nimble install https://github.com/SpotlightKid/nymph.git
|
||||
|
||||
* Edit `myplugin.lv2/manifest.ttl`:
|
||||
* Change the plugin URI.
|
||||
* Change the plugin's shared library name defined via `lv2:binary` to
|
||||
`libmyplugin.so`.
|
||||
* Change file name referenced via `rdfs:seeAlso` to `myplugin.ttl`.
|
||||
1. Make a directory named `mydsp.lv2` and copy `examples/amp.lv2/manifest.ttl`
|
||||
into it. Also copy `examples/amp.lv2/amp.ttl` to `mydsp.lv2/mydsp.ttl`.
|
||||
|
||||
* Edit `myplugin.lv2/myplugin.ttl`:
|
||||
* Change the plugin URI.
|
||||
* Define audio, control and atom ports as needed.
|
||||
1. Copy `examples/amp_plugin.nim` into your project as `mydsp_plugin.nim`.
|
||||
|
||||
* Edit `myplugin.nim`:
|
||||
* Change the `PluginUri` constant at the top.
|
||||
* Change the `PluginPort` enum and list the ports in the order defined in
|
||||
`myplugin.ttl`.
|
||||
* Rename and update the definition of the `AmpPlugin` object type and
|
||||
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.
|
||||
1. Edit `mydsp.lv2/manifest.ttl`:
|
||||
* Change the plugin URI.
|
||||
* Change the plugin's shared library name defined via `lv2:binary` to
|
||||
`libmydsp.so`.
|
||||
* Change file name referenced via `rdfs:seeAlso` to `mydsp.ttl`.
|
||||
|
||||
* 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 \
|
||||
--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
|
||||
[nymph.nimble](./nymph.nimble#L43) file on how to create a nimble task
|
||||
to simplify compilation.
|
||||
See the definition of the `build_ex` task in the
|
||||
[nymph.nimble](./nymph.nimble#L67) file on how to create a nimble task
|
||||
to simplify compilation and additional compiler args you might want to use.
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
@ -83,8 +99,17 @@ Optional:
|
|||
* [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/
|
||||
[lv2bm]: https://github.com/moddevices/lv2bm
|
||||
[lv2lint]: https://git.open-music-kontrollers.ch/~hp/lv2lint
|
||||
[Nim]: https://nim-lang.org/
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
@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 lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
|
||||
@prefix params: <http://lv2plug.in/ns/ext/parameters#> .
|
||||
@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>
|
||||
a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ;
|
||||
a lv2:Plugin , lv2:AmplifierPlugin , doap:Project ;
|
||||
|
||||
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
|||
lv2:default 0.0 ;
|
||||
lv2:minimum -90.0 ;
|
||||
lv2:maximum 20.0 ;
|
||||
unit:unit unit:db ;
|
||||
units:unit units:db ;
|
||||
];
|
||||
|
||||
rdfs:comment """
|
||||
|
|
|
@ -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#> .
|
||||
|
||||
<urn:nymph:examples:amp>
|
||||
|
|
|
@ -22,9 +22,12 @@ template db2coeff(db: cfloat): cfloat =
|
|||
|
||||
|
||||
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
|
||||
bundlePath: cstring; features: ptr ptr Lv2Feature):
|
||||
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
|
||||
Lv2Handle {.cdecl.} =
|
||||
return createShared(AmpPlugin)
|
||||
try:
|
||||
return createShared(AmpPlugin)
|
||||
except OutOfMemDefect:
|
||||
return nil
|
||||
|
||||
|
||||
proc connectPort(instance: Lv2Handle; port: cuint;
|
||||
|
@ -78,4 +81,3 @@ proc lv2Descriptor(index: cuint): ptr Lv2Descriptor {.
|
|||
result.deactivate = deactivate
|
||||
result.cleanup = cleanup
|
||||
result.extensionData = extensionData
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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);
|
|
@ -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 ⦥
|
||||
}
|
||||
|
||||
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__ */
|
|
@ -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 .
|
|
@ -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> .
|
||||
|
|
@ -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}
|
|
@ -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
|
|
@ -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> .
|
||||
|
|
@ -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 .
|
|
@ -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
|
|
@ -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 ***************************/
|
|
@ -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> .
|
||||
|
|
@ -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> .
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
@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 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 rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@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>
|
||||
a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ;
|
||||
<urn:nymph:examples:multimodefilter>
|
||||
a lv2:Plugin , lv2:FilterPlugin , doap:Project ;
|
||||
|
||||
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
|||
lv2:minimum 16.0 ;
|
||||
lv2:maximum 7000.0 ;
|
||||
lv2:portProperty props:logarithmic;
|
||||
unit:unit unit:hz ;
|
||||
units:unit units:hz ;
|
||||
],
|
||||
[
|
||||
a lv2:InputPort, lv2:ControlPort ;
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import nymph
|
||||
|
||||
import dynparamsmooth
|
||||
import paramsmooth
|
||||
import svf
|
||||
|
||||
const PluginUri = "urn:nymph:examples:multimode-filter"
|
||||
const PluginUri = "urn:nymph:examples:multimodefilter"
|
||||
|
||||
type
|
||||
SampleBuffer = UncheckedArray[cfloat]
|
||||
|
@ -19,17 +19,20 @@ type
|
|||
cutoff: ptr cfloat
|
||||
q: ptr cfloat
|
||||
mode: ptr cfloat
|
||||
svf: FilterSV
|
||||
smoothCutoff: DynParamSmooth
|
||||
svf: SVFilter
|
||||
smoothCutoff: ParamSmooth
|
||||
|
||||
|
||||
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
|
||||
bundlePath: cstring; features: ptr ptr Lv2Feature):
|
||||
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
|
||||
Lv2Handle {.cdecl.} =
|
||||
let plug = createShared(SVFPlugin)
|
||||
plug.svf = initFilterSV(fmLowPass, sampleRate)
|
||||
plug.smoothCutoff = initDynParamSmooth(sampleRate=sampleRate)
|
||||
return plug
|
||||
try:
|
||||
let plug = createShared(SVFPlugin)
|
||||
plug.svf = initSVFilter(fmLowPass, sampleRate)
|
||||
plug.smoothCutoff = initParamSmooth(20.0, sampleRate)
|
||||
return cast[Lv2Handle](plug)
|
||||
except OutOfMemDefect:
|
||||
return nil
|
||||
|
||||
|
||||
proc connectPort(instance: Lv2Handle; port: cuint;
|
|
@ -24,7 +24,7 @@ proc setSampleRate*(self: var ParamSmooth, sampleRate: float64) =
|
|||
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)
|
||||
return self.z
|
||||
|
||||
|
|
|
@ -9,32 +9,32 @@ type
|
|||
FilterMode* = enum
|
||||
fmLowPass, fmHighPass, fmBandPass, fmBandReject
|
||||
|
||||
FilterSV* = object
|
||||
SVFilter* = object
|
||||
mode: FilterMode
|
||||
cutoff, q, lowPass, hiPass, bandPass, bandReject, a, b, maxCutoff: float
|
||||
sampleRate: float64
|
||||
needs_update: bool
|
||||
needsUpdate: bool
|
||||
|
||||
|
||||
proc reset*(self: var FilterSV) =
|
||||
proc reset*(self: var SVFilter) =
|
||||
self.lowPass = 0.0
|
||||
self.hiPass = 0.0
|
||||
self.bandPass = 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.sampleRate = sampleRate
|
||||
result.reset()
|
||||
result.a = 0.0
|
||||
result.b = 0.0
|
||||
result.maxCutoff = sampleRate / 6.0
|
||||
result.needs_update = true
|
||||
result.needsUpdate = true
|
||||
|
||||
|
||||
proc calcCoef*(self: var FilterSV) =
|
||||
if self.needs_update:
|
||||
proc calcCoef*(self: var SVFilter) =
|
||||
if self.needsUpdate:
|
||||
self.a = 2.0 * sin(PI * self.cutoff / self.sampleRate)
|
||||
|
||||
if self.q > 0.0:
|
||||
|
@ -42,36 +42,36 @@ proc calcCoef*(self: var FilterSV) =
|
|||
else:
|
||||
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)
|
||||
|
||||
if fc != self.cutoff:
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
proc setSampleRate*(self: var FilterSV, sampleRate: float) =
|
||||
proc setSampleRate*(self: var SVFilter, sampleRate: float) =
|
||||
if sampleRate != self.sampleRate:
|
||||
self.sampleRate = sampleRate
|
||||
self.needs_update = true
|
||||
self.needsUpdate = true
|
||||
self.reset()
|
||||
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.hiPass = sample - (self.lowPass + (self.b * self.bandPass))
|
||||
self.bandPass += self.a * self.hiPass
|
||||
|
|
|
@ -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> .
|
||||
|
|
@ -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 .
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
13
nymph.nimble
13
nymph.nimble
|
@ -25,7 +25,10 @@ type Example = tuple
|
|||
|
||||
const examples = to_table({
|
||||
"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], "")
|
||||
|
||||
let examplesDir = thisDir() / "examples"
|
||||
result.source = examplesDir / changeFileExt(result.name, "nim")
|
||||
result.source = examplesDir / result.name & "_plugin.nim"
|
||||
|
||||
if not fileExists(result.source):
|
||||
quit(&"Example '{result.name}' not found.")
|
||||
|
@ -69,6 +72,9 @@ task build_ex, "Build given example plugin":
|
|||
switch("mm", "arc")
|
||||
switch("out", ex.dll)
|
||||
|
||||
when defined(gcc):
|
||||
switch("passC", "-fvisibility=hidden")
|
||||
|
||||
when not defined(release) and not defined(debug):
|
||||
echo &"Compiling plugin {ex.name} in release mode."
|
||||
switch("define", "release")
|
||||
|
@ -83,7 +89,7 @@ task lv2lint, "Run lv2lint check on given example plugin":
|
|||
let ex = getExample("lv2lint")
|
||||
|
||||
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:
|
||||
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}\"")
|
||||
else:
|
||||
echo &"Example '{ex.name}' shared library not found. Use task 'build_ex' to build it."
|
||||
|
||||
|
|
|
@ -1,32 +1,2 @@
|
|||
const LV2_CORE_URI* = "http://lv2plug.in/ns/lv2core"
|
||||
|
||||
|
||||
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.}
|
||||
|
||||
import nymph/core
|
||||
export core
|
|
@ -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.
|
|
@ -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.
|
||||
##
|
|
@ -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.}
|
||||
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
plugin:
|
||||
urid: urn:foobar#v1
|
||||
port_group:
|
||||
name: "stereo"
|
||||
audio_input:
|
||||
"in_l"
|
||||
audio_input:
|
||||
"in_r"
|
|
@ -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)))
|
|
@ -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.}
|
|
@ -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.
|
Loading…
Reference in New Issue