From 521fefc5313d14598bc254088b6b2db640642246 Mon Sep 17 00:00:00 2001 From: Christopher Arndt Date: Sun, 29 Sep 2024 13:47:26 +0200 Subject: [PATCH] feat: add faustlpf example Shows how to use DSP code in C generated from FAUST code Signed-off-by: Christopher Arndt --- examples/faustlpf.lv2/faustlpf.ttl | 58 +++++ examples/faustlpf.lv2/manifest.ttl | 8 + examples/faustlpf_plugin.nim | 105 ++++++++ examples/lpf.dsp | 12 + examples/lpf.h | 395 +++++++++++++++++++++++++++++ examples/minarch.h | 25 ++ nymph.nimble | 1 + 7 files changed, 604 insertions(+) create mode 100644 examples/faustlpf.lv2/faustlpf.ttl create mode 100644 examples/faustlpf.lv2/manifest.ttl create mode 100644 examples/faustlpf_plugin.nim create mode 100644 examples/lpf.dsp create mode 100644 examples/lpf.h create mode 100644 examples/minarch.h diff --git a/examples/faustlpf.lv2/faustlpf.ttl b/examples/faustlpf.lv2/faustlpf.ttl new file mode 100644 index 0000000..f704e4b --- /dev/null +++ b/examples/faustlpf.lv2/faustlpf.ttl @@ -0,0 +1,58 @@ +@prefix bufs: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix opts: . +@prefix params: . +@prefix props: . +@prefix rdfs: . +@prefix units: . + + + 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 ; + + doap:maintainer [ + foaf:name "Christopher Arndt" ; + foaf:mbox ; + foaf:homepage ; + ] ; + + lv2:microVersion 0 ; + lv2:minorVersion 1 . diff --git a/examples/faustlpf.lv2/manifest.ttl b/examples/faustlpf.lv2/manifest.ttl new file mode 100644 index 0000000..8063d91 --- /dev/null +++ b/examples/faustlpf.lv2/manifest.ttl @@ -0,0 +1,8 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + diff --git a/examples/faustlpf_plugin.nim b/examples/faustlpf_plugin.nim new file mode 100644 index 0000000..542e18d --- /dev/null +++ b/examples/faustlpf_plugin.nim @@ -0,0 +1,105 @@ +## A FAUST standard library 2-pole lowpass filter LV2 plugin + +import nymph + +type + SampleBuffer = UncheckedArray[cfloat] + +{.emit: "#include \"lpf.h\"".} +type + faustlpf {.importc, header: "lpf.h".}= object + fHslider0: 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 getNumInputsmydsp(dsp: ptr faustlpf): cint {.importc.} +#proc getNumOutputsmydsp(dsp: ptr faustlpf): cint {.importc.} +#proc getSampleRatefaustlpf(dsp: ptr faustlpf): cint {.importc.} +proc computefaustlpf(dsp: ptr faustlpf, count: cint, inputs, outputs: ptr ptr SampleBuffer) {.importc.} + +type + PluginPort {.pure.} = enum + Input, Output, Frequency + + FaustLPFPlugin = object + input: ptr SampleBuffer + output: ptr SampleBuffer + freq: ptr cfloat + flt: ptr faustlpf + + +const + PluginUri = "urn:nymph:examples:faustlpf" + minFreq = 16.0 + maxFreq = 15_000.0 + + +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.fHslider0 = plug.freq[].clamp(minFreq, maxFreq) + + 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 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 diff --git a/examples/lpf.dsp b/examples/lpf.dsp new file mode 100644 index 0000000..c2d3725 --- /dev/null +++ b/examples/lpf.dsp @@ -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); diff --git a/examples/lpf.h b/examples/lpf.h new file mode 100644 index 0000000..e50c469 --- /dev/null +++ b/examples/lpf.h @@ -0,0 +1,395 @@ +/* ------------------------------------------------------------ +author: "Christopher Arndt" +copyright: "Christopher Arndt, 2024" +license: "MIT" +name: "FaustLPF" +version: "0.1.0" +Code generated with Faust 2.74.6 (https://faust.grame.fr) +Compilation options: -a ./examples/minarch.h -lang c -ct 1 -cn faustlpf -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 -vec -lv 0 -vs 32 +------------------------------------------------------------ */ + +#ifndef __faustlpf_H__ +#define __faustlpf_H__ + +/******************* BEGIN minarch.h ****************/ + +/************************************************************************ + FAUST Architecture File for generating a very minimal C interface + ************************************************************************/ + +#include +#include + +#include "faust/gui/CInterface.h" + +#define max(a,b) ((a < b) ? b : a) +#define min(a,b) ((a < b) ? a : b) + +/****************************************************************************** + VECTOR INTRINSICS +*******************************************************************************/ + + +/**************************BEGIN USER SECTION **************************/ + +#ifndef FAUSTFLOAT +#define FAUSTFLOAT float +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) +#define RESTRICT __restrict +#else +#define RESTRICT __restrict__ +#endif + +#include +#include +#include + +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 + +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() { + 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 ./examples/minarch.h -lang c -ct 1 -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", "lpf.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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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.5.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 * (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] = 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] = 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 USER SECTION ***************************/ + +#endif diff --git a/examples/minarch.h b/examples/minarch.h new file mode 100644 index 0000000..6b8fef4 --- /dev/null +++ b/examples/minarch.h @@ -0,0 +1,25 @@ +/******************* BEGIN minarch.h ****************/ + +/************************************************************************ + FAUST Architecture File for generating a very minimal C interface + ************************************************************************/ + +#include +#include + +#include "faust/gui/CInterface.h" + +#define max(a,b) ((a < b) ? b : a) +#define min(a,b) ((a < b) ? a : b) + +/****************************************************************************** + VECTOR INTRINSICS +*******************************************************************************/ + +<> + +/**************************BEGIN USER SECTION **************************/ + +<> + +/***************************END USER SECTION ***************************/ diff --git a/nymph.nimble b/nymph.nimble index 41caee5..9a78f31 100644 --- a/nymph.nimble +++ b/nymph.nimble @@ -25,6 +25,7 @@ type Example = tuple const examples = to_table({ "amp": "urn:nymph:examples:amp", + "faustlpf": "urn:nymph:examples:faustlpf", "miditranspose": "urn:nymph:examples:miditranspose", "multimodefilter": "urn:nymph:examples:multimodefilter", "tiltfilter": "urn:nymph:examples:tiltfilter",