Add multi-mode state-variable filter example plugin
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
parent
f76ec9a1ef
commit
8fc3663bc3
|
@ -0,0 +1,8 @@
|
||||||
|
@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,95 @@
|
||||||
|
@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 unit: <http://lv2plug.in/ns/extensions/units#> .
|
||||||
|
|
||||||
|
<urn:nymph:examples:multimode-filter>
|
||||||
|
a lv2:Plugin, lv2:AmplifierPlugin , 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 7000.0 ;
|
||||||
|
lv2:minimum 16.0 ;
|
||||||
|
lv2:maximum 7000.0 ;
|
||||||
|
lv2:portProperty props:logarithmic;
|
||||||
|
unit:unit unit:hz ;
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 3 ;
|
||||||
|
lv2:name "Q" ;
|
||||||
|
lv2:symbol "q" ;
|
||||||
|
lv2:default 0.8 ;
|
||||||
|
lv2:minimum 0.8 ;
|
||||||
|
lv2:maximum 10.0 ;
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 4 ;
|
||||||
|
lv2:name "Filter mode" ;
|
||||||
|
lv2:symbol "mode" ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 3 ;
|
||||||
|
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:label "Bandreject" ;
|
||||||
|
rdf:value 3 ;
|
||||||
|
] ;
|
||||||
|
];
|
||||||
|
|
||||||
|
rdfs:comment """
|
||||||
|
A multi-mode (LPF/HPF/BPF/Notch) audio filter.
|
||||||
|
""" ;
|
||||||
|
|
||||||
|
doap:name "nymph multi-mode 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,97 @@
|
||||||
|
## A simple multi-mode audio filter LV2 plugin
|
||||||
|
|
||||||
|
import nymph
|
||||||
|
|
||||||
|
import svf
|
||||||
|
|
||||||
|
const PluginUri = "urn:nymph:examples:multimode-filter"
|
||||||
|
|
||||||
|
type
|
||||||
|
SampleBuffer = UncheckedArray[cfloat]
|
||||||
|
|
||||||
|
PluginPort {.pure.} = enum
|
||||||
|
Input, Output, Cutoff, Q, Mode
|
||||||
|
|
||||||
|
SVFPlugin = object
|
||||||
|
input: ptr SampleBuffer
|
||||||
|
output: ptr SampleBuffer
|
||||||
|
cutoff: ptr cfloat
|
||||||
|
q: ptr cfloat
|
||||||
|
mode: ptr cfloat
|
||||||
|
svf: FilterSV
|
||||||
|
|
||||||
|
|
||||||
|
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
|
||||||
|
bundlePath: cstring; features: ptr ptr Lv2Feature):
|
||||||
|
Lv2Handle {.cdecl.} =
|
||||||
|
var plug = createShared(SVFPlugin)
|
||||||
|
plug.svf = initFilterSV(fmLowPass, sampleRate)
|
||||||
|
return plug
|
||||||
|
|
||||||
|
|
||||||
|
proc connectPort(instance: Lv2Handle; port: cuint;
|
||||||
|
dataLocation: pointer) {.cdecl.} =
|
||||||
|
let plug = cast[ptr SVFPlugin](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.Cutoff:
|
||||||
|
plug.cutoff = cast[ptr cfloat](dataLocation)
|
||||||
|
of PluginPort.Q:
|
||||||
|
plug.q = cast[ptr cfloat](dataLocation)
|
||||||
|
of PluginPort.Mode:
|
||||||
|
plug.mode = cast[ptr cfloat](dataLocation)
|
||||||
|
|
||||||
|
|
||||||
|
proc activate(instance: Lv2Handle) {.cdecl.} =
|
||||||
|
let plug = cast[ptr SVFPlugin](instance)
|
||||||
|
plug.svf.reset()
|
||||||
|
plug.svf.setMode(fmLowPass)
|
||||||
|
plug.svf.setCutoff(7_000.0)
|
||||||
|
plug.svf.setQ(0.8)
|
||||||
|
plug.svf.calcCoef()
|
||||||
|
|
||||||
|
|
||||||
|
proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} =
|
||||||
|
let plug = cast[ptr SVFPlugin](instance)
|
||||||
|
plug.svf.setMode(plug.mode[].int.clamp(0, 4).FilterMode)
|
||||||
|
plug.svf.setCutoff(plug.cutoff[].clamp(16.0, 7_000.0))
|
||||||
|
plug.svf.setQ(plug.q[].clamp(0.8, 10.0))
|
||||||
|
plug.svf.calcCoef()
|
||||||
|
|
||||||
|
for pos in 0 ..< nSamples:
|
||||||
|
plug.output[pos] = plug.svf.process(plug.input[pos])
|
||||||
|
|
||||||
|
|
||||||
|
proc deactivate(instance: Lv2Handle) {.cdecl.} =
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
proc cleanup(instance: Lv2Handle) {.cdecl.} =
|
||||||
|
freeShared(cast[ptr SVFPlugin](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,95 @@
|
||||||
|
## State Variable Filter after Hal Chamberlin, Musical Applications of Microprocessors
|
||||||
|
## Only stable up to fc ~= fs / 6.5 !
|
||||||
|
|
||||||
|
import std/math
|
||||||
|
|
||||||
|
type
|
||||||
|
FilterMode* = enum
|
||||||
|
fmLowPass, fmHighPass, fmBandPass, fmBandReject
|
||||||
|
|
||||||
|
FilterSV* = object
|
||||||
|
mode: FilterMode
|
||||||
|
cutoff: float
|
||||||
|
q: float
|
||||||
|
lowPass: float
|
||||||
|
hiPass: float
|
||||||
|
bandPass: float
|
||||||
|
bandReject: float
|
||||||
|
a: float
|
||||||
|
b: float
|
||||||
|
maxCutoff: float
|
||||||
|
sampleRate: float
|
||||||
|
needs_update: bool
|
||||||
|
|
||||||
|
|
||||||
|
proc reset*(self: var FilterSV) =
|
||||||
|
self.lowPass = 0
|
||||||
|
self.hiPass = 0
|
||||||
|
self.bandPass = 0
|
||||||
|
self.bandReject = 0
|
||||||
|
|
||||||
|
|
||||||
|
proc initFilterSV*(mode: FilterMode = fmLowPass, sampleRate: float = 48_000): FilterSV =
|
||||||
|
result.mode = mode
|
||||||
|
result.sampleRate = sampleRate
|
||||||
|
result.reset()
|
||||||
|
result.a = 0
|
||||||
|
result.b = 0
|
||||||
|
result.maxCutoff = sampleRate / 6.0
|
||||||
|
result.needs_update = true
|
||||||
|
|
||||||
|
|
||||||
|
proc calcCoef*(self: var FilterSV) =
|
||||||
|
if self.needs_update:
|
||||||
|
self.a = 2.0 * sin(PI * self.cutoff / self.sampleRate)
|
||||||
|
|
||||||
|
if self.q > 0.0:
|
||||||
|
self.b = 1.0 / self.q
|
||||||
|
else:
|
||||||
|
self.b = 0.0
|
||||||
|
|
||||||
|
self.needs_update = false
|
||||||
|
|
||||||
|
|
||||||
|
proc setCutoff*(self: var FilterSV, cutoff: float) =
|
||||||
|
let fc = if cutoff > self.maxCutoff: self.maxCutoff else: cutoff
|
||||||
|
|
||||||
|
if fc != self.cutoff:
|
||||||
|
self.cutoff = fc
|
||||||
|
self.needs_update = true
|
||||||
|
|
||||||
|
|
||||||
|
proc setQ*(self: var FilterSV, q: float) =
|
||||||
|
if q != self.q:
|
||||||
|
self.q = q
|
||||||
|
self.needs_update = true
|
||||||
|
|
||||||
|
|
||||||
|
proc setMode*(self: var FilterSV, mode: FilterMode) =
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
|
|
||||||
|
proc setSampleRate*(self: var FilterSV, sampleRate: float) =
|
||||||
|
if sampleRate != self.sampleRate:
|
||||||
|
self.sampleRate = sampleRate
|
||||||
|
self.needs_update = true
|
||||||
|
self.reset()
|
||||||
|
self.calcCoef()
|
||||||
|
|
||||||
|
|
||||||
|
proc process*(self: var FilterSV, sample: float): float =
|
||||||
|
self.lowPass += self.a * self.bandPass
|
||||||
|
self.hiPass = sample - (self.lowPass + (self.b * self.bandPass))
|
||||||
|
self.bandPass += self.a * self.hiPass
|
||||||
|
self.bandReject = self.hiPass + self.lowPass
|
||||||
|
|
||||||
|
case self.mode:
|
||||||
|
of fmLowPass:
|
||||||
|
return self.lowPass
|
||||||
|
of fmHighPass:
|
||||||
|
return self.hiPass
|
||||||
|
of fmBandPass:
|
||||||
|
return self.bandPass
|
||||||
|
of fmBandReject:
|
||||||
|
return self.bandReject
|
||||||
|
|
|
@ -24,7 +24,8 @@ type Example = tuple
|
||||||
|
|
||||||
|
|
||||||
const examples = to_table({
|
const examples = to_table({
|
||||||
"amp": "urn:nymph:examples:amp"
|
"amp": "urn:nymph:examples:amp",
|
||||||
|
"multimode_filter": "urn:nymph:examples:multimode-filter",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue