Compare commits

...

2 Commits

Author SHA1 Message Date
Christopher Arndt 46e4fda581 Add stub readme and license file
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-04-20 02:59:22 +02:00
Christopher Arndt cf10456f18 Add nimble tasks and example 'amp' plugin
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2024-04-20 02:59:22 +02:00
10 changed files with 313 additions and 28 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
nimble.paths
*.so
*.dll
/.lv2/

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2024 Christopher Arndt <chris@chrisarndt.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# nymph
A [Nim] library for writing audio and MIDI plugins conforming to the [LV2] standard
[LV2]: https://lv2plug.in/
[Nim]: https://nim-lang.org/

59
examples/amp.lv2/amp.ttl Normal file
View File

@ -0,0 +1,59 @@
@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 rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix unit: <http://lv2plug.in/ns/extensions/units#> .
<urn:nymph:examples:amp>
a lv2:Plugin, lv2:AmplifierPlugin , doap:Project ;
lv2:optionalFeature lv2:hardRTCapable , bufs:boundedBlockLength , opts:options ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> ;
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 <https://spdx.org/licenses/MIT> ;
doap:maintainer [
foaf:name "Christopher Arndt" ;
foaf:mbox <mailto:info@chrisarndt.de> ;
foaf:homepage <https://gitlab.com/SpotlightKid/nymph> ;
] ;
lv2:microVersion 0 ;
lv2:minorVersion 1 .

View File

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

82
examples/amp.nim Normal file
View File

@ -0,0 +1,82 @@
import std/math
import nymph
const PluginUri = "urn:nymph:examples:amp"
type
CSample = cfloat
CSamples = UncheckedArray[CSample]
PortIndex = enum
INPUT = 0,
OUTPUT = 1,
GAIN = 2
Amp = object
input: ptr CSamples
output: ptr CSamples
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(Amp)
proc connectPort(instance: Lv2Handle; port: cuint;
dataLocation: pointer) {.cdecl.} =
let amp = cast[ptr Amp](instance)
case cast[PortIndex](port)
of INPUT:
amp.input = cast[ptr CSamples](dataLocation)
of OUTPUT:
amp.output = cast[ptr CSamples](dataLocation)
of GAIN:
amp.gain = cast[ptr cfloat](dataLocation)
proc activate(instance: Lv2Handle) {.cdecl.} =
discard
proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} =
let amp = cast[ptr Amp](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 Amp](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

1
examples/config.nims Normal file
View File

@ -0,0 +1 @@
switch("path", "$projectDir/../src")

View File

@ -1,4 +1,5 @@
# Package # Package
import std/strformat
version = "0.1.0" version = "0.1.0"
author = "Christopher Arndt" author = "Christopher Arndt"
@ -10,3 +11,118 @@ srcDir = "src"
# Dependencies # Dependencies
requires "nim >= 2.0" requires "nim >= 2.0"
# Custom tasks
const examples = to_table({
"amp": "urn:nymph:examples:amp"
})
## Parse task specific command line arguments into option switches and positional arguments
proc parseArgs(): tuple[options: seq[string], args: seq[string]] =
for arg in commandLineParams:
if arg[0] == '-': # -d:foo or --define:foo
result.options.add(arg)
else:
result.args.add(arg)
## Show task environment (for debugging when writing nimble tasks)
proc showArgs() =
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 <example name>"
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 <example name>"
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 <example name>"
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."

View File

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

View File

@ -1 +1,3 @@
switch("path", "$projectDir/../src") switch("path", "$projectDir/../src")
switch("warning[BareExcept]", "off")
switch("warning[UnusedImport]", "off")