From a151e962330998d4d0a957dd5e05b3251b2d2940 Mon Sep 17 00:00:00 2001 From: Christopher Arndt Date: Sat, 20 Apr 2024 02:50:10 +0200 Subject: [PATCH] Add nimble tasks and example 'amp' plugin Signed-off-by: Christopher Arndt --- .gitignore | 4 ++ examples/amp.lv2/amp.ttl | 59 +++++++++++++++++ examples/amp.lv2/manifest.ttl | 8 +++ examples/amp.nim | 81 +++++++++++++++++++++++ examples/config.nims | 1 + nymph.nimble | 119 +++++++++++++++++++++++++++++++++- src/nymph.nim | 40 ++++-------- tests/config.nims | 4 +- 8 files changed, 286 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 examples/amp.lv2/amp.ttl create mode 100644 examples/amp.lv2/manifest.ttl create mode 100644 examples/amp.nim create mode 100644 examples/config.nims diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb778fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +nimble.paths +*.so +*.dll +/.lv2/ diff --git a/examples/amp.lv2/amp.ttl b/examples/amp.lv2/amp.ttl new file mode 100644 index 0000000..2bbadb6 --- /dev/null +++ b/examples/amp.lv2/amp.ttl @@ -0,0 +1,59 @@ +@prefix bufs: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix opts: . +@prefix params: . +@prefix rdfs: . +@prefix unit: . + + + a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ; + + lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ; + + lv2:requiredFeature ; + + 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 "Gain" ; + lv2:symbol "gain" ; + lv2:default 0.0 ; + lv2:minimum -90.0 ; + lv2:maximum 20.0 ; + unit:unit unit:db ; + ]; + + rdfs:comment """ +A simple amplifier / attenuator. +""" ; + + doap:name "nymph example amp" ; + doap:license ; + + doap:maintainer [ + foaf:name "Christopher Arndt" ; + foaf:mbox ; + foaf:homepage ; + ] ; + + lv2:microVersion 0 ; + lv2:minorVersion 1 . + diff --git a/examples/amp.lv2/manifest.ttl b/examples/amp.lv2/manifest.ttl new file mode 100644 index 0000000..ecbc919 --- /dev/null +++ b/examples/amp.lv2/manifest.ttl @@ -0,0 +1,8 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + diff --git a/examples/amp.nim b/examples/amp.nim new file mode 100644 index 0000000..9ff9775 --- /dev/null +++ b/examples/amp.nim @@ -0,0 +1,81 @@ +## A simple amplifier LV2 plugin + +import std/math +import nymph + +const PluginUri = "urn:nymph:examples:amp" + +type + SampleBuffer = UncheckedArray[cfloat] + + PluginPort {.pure.} = enum + Input, Output, Gain + + AmpPlugin = object + input: ptr SampleBuffer + output: ptr SampleBuffer + gain: ptr cfloat + + +template db2coeff(db: cfloat): cfloat = + pow(10.0, db / 20.0) + + +proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble; + bundlePath: cstring; features: ptr ptr Lv2Feature): + Lv2Handle {.cdecl.} = + return createShared(AmpPlugin) + + +proc connectPort(instance: Lv2Handle; port: cuint; + dataLocation: pointer) {.cdecl.} = + let amp = cast[ptr AmpPlugin](instance) + case cast[PluginPort](port) + of PluginPort.Input: + amp.input = cast[ptr SampleBuffer](dataLocation) + of PluginPort.Output: + amp.output = cast[ptr SampleBuffer](dataLocation) + of PluginPort.Gain: + amp.gain = cast[ptr cfloat](dataLocation) + + +proc activate(instance: Lv2Handle) {.cdecl.} = + discard + + +proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} = + let amp = cast[ptr AmpPlugin](instance) + for pos in 0 ..< nSamples: + amp.output[pos] = amp.input[pos] * db2coeff(amp.gain[]) + + +proc deactivate(instance: Lv2Handle) {.cdecl.} = + discard + + +proc cleanup(instance: Lv2Handle) {.cdecl.} = + freeShared(cast[ptr AmpPlugin](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 + diff --git a/examples/config.nims b/examples/config.nims new file mode 100644 index 0000000..80091ff --- /dev/null +++ b/examples/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src") diff --git a/nymph.nimble b/nymph.nimble index ca0be21..e7f462e 100644 --- a/nymph.nimble +++ b/nymph.nimble @@ -1,4 +1,6 @@ -# Package +import std/strformat + +# Package definition version = "0.1.0" author = "Christopher Arndt" @@ -6,7 +8,120 @@ description = "A Nim library for writing audio and MIDI plugins conforming to license = "MIT" srcDir = "src" - # Dependencies requires "nim >= 2.0" + +# Custom tasks + +const examples = to_table({ + "amp": "urn:nymph:examples:amp" +}) + + +proc parseArgs(): tuple[options: seq[string], args: seq[string]] = + ## Parse task specific command line arguments into option switches and positional arguments + for arg in commandLineParams: + if arg[0] == '-': # -d:foo or --define:foo + result.options.add(arg) + else: + result.args.add(arg) + + +proc showArgs() = + ## Show task environment (for debugging when writing nimble tasks) + echo "Command: ", getCommand() + echo "ProjectName: ", projectName() + echo "ProjectDir: ", projectDir() + echo "ProjectPath: ", projectPath() + echo "Task args: ", commandLineParams + + for i in 0..paramCount(): + echo &"Arg {i}: ", paramStr(i) + + +task build_ex, "Build given example plugin": + #showArgs() + let (_, args) = parseArgs() + + if args.len == 0: + echo "Usage: nimble build_ex " + return + + let example = args[^1] + let source = thisDir() & "/examples/" & example & ".nim" + let bundle = thisDir() & "/examples/" & example & ".lv2" + let dll = bundle & "/" & toDll(example) + + if fileExists(source): + switch("app", "lib") + switch("noMain", "on") + switch("mm", "arc") + switch("opt", "speed") + switch("define", "release") + switch("out", dll) + setCommand("compile", source) + else: + echo &"Example '{example}' not found." + + +task lv2lint, "Run lv2lint check on given example plugin": + let (_, args) = parseArgs() + + if args.len == 0: + echo "Usage: nimble lv2lint " + return + + let example = args[^1] + let uri = examples.getOrDefault(example) + + if uri == "": + echo &"Plugin URI for example '{example}' not set." + return + + let examplesDir = thisDir() & "/examples" + let bundle = examplesDir & "/" & example & ".lv2" + let dll = bundle & "/" & toDll(example) + + if fileExists(dll): + exec(&"lv2lint -s NimMain -I {bundle} \"{uri}\"") + else: + echo &"Example '{example}' shared library not found. Use task 'build_ex' to build it." + + +task lv2bm, "Run lv2bm benchmark on given example plugin": + let (_, args) = parseArgs() + + if args.len == 0: + echo "Usage: nimble lv2bm " + return + + let example = args[^1] + let uri = examples.getOrDefault(example) + + if uri == "": + echo &"Plugin URI for example '{example}' not set." + return + + let examplesDir = thisDir() & "/examples" + let bundle = examplesDir & "/" & example & ".lv2" + let dll = bundle & "/" & toDll(example) + + if fileExists(dll): + let lv2_path = getEnv("LV2_PATH") + let tempLv2Dir = thisDir() & "/.lv2" + let bundleLink = tempLv2Dir & "/" & example & ".lv2" + + mkDir(tempLv2Dir) + rmFile(bundleLink) + exec(&"ln -s \"{bundle}\" \"{bundleLink}\"") + + if lv2_path == "": + putEnv("LV2_PATH", tempLv2Dir) + else: + putEnv("LV2_PATH", tempLv2Dir & ":" & lv2_path) + + exec(&"lv2bm --full-test -i white \"{uri}\"") + else: + echo &"Example '{example}' shared library not found. Use task 'build_ex' to build it." + diff --git a/src/nymph.nim b/src/nymph.nim index 034a555..11c5abb 100644 --- a/src/nymph.nim +++ b/src/nymph.nim @@ -1,46 +1,32 @@ 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.} + instantiate*: proc(descriptor: ptr Lv2Descriptor, sampleRate: cdouble, bundlePath: cstring, + features: ptr ptr Lv2Feature): Lv2Handle {.cdecl.} - connectPort*: proc( - instance: Lv2Handle, - port: cuint, - dataLocation: pointer - ) {.cdecl.} + connectPort*: proc(instance: Lv2Handle, port: cuint, dataLocation: pointer) {.cdecl.} - activate*: proc( - instance: Lv2Handle - ) {.cdecl.} + activate*: proc(instance: Lv2Handle) {.cdecl.} - run*: proc( - instance: Lv2Handle, - sampleCount: cuint - ) {.cdecl.} + run*: proc(instance: Lv2Handle, sampleCount: cuint) {.cdecl.} - deactivate*: proc( - instance: Lv2Handle - ) {.cdecl.} + deactivate*: proc(instance: Lv2Handle) {.cdecl.} - cleanup*: proc( - instance: Lv2Handle - ) {.cdecl.} + cleanup*: proc(instance: Lv2Handle) {.cdecl.} + + extensionData*: proc(uri: cstring): pointer {.cdecl.} - extensionData*: proc( - uri: cstring - ): pointer {.cdecl.} type lv2Descriptor* = proc(index: cuint): ptr Lv2Descriptor {.cdecl.} + diff --git a/tests/config.nims b/tests/config.nims index 3bb69f8..5e3397c 100644 --- a/tests/config.nims +++ b/tests/config.nims @@ -1 +1,3 @@ -switch("path", "$projectDir/../src") \ No newline at end of file +switch("path", "$projectDir/../src") +switch("warning[BareExcept]", "off") +switch("warning[UnusedImport]", "off")