Implement MIDI procesing (WIP)
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
parent
ff6ddf77d2
commit
ba6aaa22ab
|
@ -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,98 @@
|
|||
## A simple MIDI event processor LV2 plugin
|
||||
|
||||
import std/[math, strformat, 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
|
||||
|
||||
|
||||
proc printFeatures(features: ptr UncheckedArray[ptr Lv2Feature]) =
|
||||
if features != nil:
|
||||
var i = 0
|
||||
while true:
|
||||
let feature = features[i]
|
||||
if feature == nil:
|
||||
break
|
||||
echo &"URI: {feature[].uri}"
|
||||
echo &"Data: {cast[int](feature[].data)}"
|
||||
inc i
|
||||
|
||||
|
||||
proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble;
|
||||
bundlePath: cstring; features: ptr UncheckedArray[ptr Lv2Feature]):
|
||||
Lv2Handle {.cdecl.} =
|
||||
printFeatures(features)
|
||||
let amp: ptr MidiTransposePlugin = createShared(MidiTransposePlugin)
|
||||
amp.map = cast[ptr UridMap](lv2FeaturesData(features, lv2UridMap))
|
||||
assert amp.map != nil
|
||||
amp.midi_urid = amp.map[].map(amp.map[].handle, lv2MidiMidiEvent)
|
||||
echo &"{lv2MidiMidiEvent} = {amp.midi_urid.int}"
|
||||
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)
|
||||
if amp.input.atom.size > 8:
|
||||
for ev in items(amp.input):
|
||||
if Urid(ev.body.`type`) == amp.midi_urid:
|
||||
# TODO
|
||||
var msg = cast[ptr UncheckedArray[uint8]](ev.body.addr + 1)
|
||||
echo &"0x{toHex(msg[0], 2)} 0x{toHex(msg[1], 2)} 0x{toHex(msg[2], 2)}"
|
||||
|
||||
|
||||
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
|
|
@ -26,6 +26,7 @@ type Example = tuple
|
|||
const examples = to_table({
|
||||
"amp": "urn:nymph:examples:amp",
|
||||
"multimode_filter": "urn:nymph:examples:multimode-filter",
|
||||
"miditranspose": "urn:nymph:examples:miditranspose",
|
||||
})
|
||||
|
||||
|
||||
|
@ -83,7 +84,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."
|
||||
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
## 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.
|
||||
##
|
||||
|
||||
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`)
|
||||
|
||||
#[
|
||||
{.push header: "lv2/atom/atom.h".}
|
||||
|
||||
var atomReferenceType* {.importc: "LV2_ATOM_REFERENCE_TYPE".}: int
|
||||
|
||||
##
|
||||
## Const version of LV2_ATOM_CONTENTS.
|
||||
##
|
||||
proc atomContentsConst*(`type`: untyped; atom: untyped) {.importc: "LV2_ATOM_CONTENTS_CONST".}
|
||||
|
||||
##
|
||||
## Return a pointer to the body of an Atom. The "body" of an atom is the
|
||||
## data just past the LV2_Atom head (i.e. the same offset for all types).
|
||||
##
|
||||
proc atomBody*(atom: untyped) {.importc: "LV2_ATOM_BODY".}
|
||||
|
||||
##
|
||||
## Const version of LV2_ATOM_BODY.
|
||||
##
|
||||
proc atomBodyConst*(atom: untyped) {.importc: "LV2_ATOM_BODY_CONST".}
|
||||
|
||||
{.pop.}
|
||||
]#
|
||||
|
||||
type
|
||||
## The header of an atom:Atom.
|
||||
Atom* {.bycopy.} = object
|
||||
size*: uint32 ## Size in bytes, not including type and size.
|
||||
`type`*: uint32 ## of this atom (mapped URI).
|
||||
|
||||
## An atom:Int or atom:Bool. May be cast to LV2_Atom.
|
||||
AtomInt* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: int32 ## Integer value.
|
||||
|
||||
## An atom:Long. May be cast to LV2_Atom.
|
||||
AtomLong* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: int64 ## Integer value.
|
||||
|
||||
## An atom:Float. May be cast to LV2_Atom.
|
||||
AtomFloat* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: cfloat ## Floating point value.
|
||||
|
||||
## An atom:Double. May be cast to LV2_Atom.
|
||||
AtomDouble* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: cdouble ## Floating point value.
|
||||
|
||||
## An atom:Bool. May be cast to LV2_Atom.
|
||||
AtomBool* = distinct AtomInt
|
||||
|
||||
## An atom:URID. May be cast to LV2_Atom.
|
||||
AtomUrid* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: uint32 ## URID.
|
||||
|
||||
## An atom:String. May be cast to LV2_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*: uint32 ## DataURID.
|
||||
lang*: uint32 ## Language URID.
|
||||
## Contents (a null-terminated UTF-8 string) follow here.
|
||||
|
||||
## An atom:Literal. May be cast to LV2_Atom.
|
||||
AtomLiteral* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: AtomLiteralBody ## Body.
|
||||
|
||||
## An atom:Tuple. May be cast to LV2_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 LV2_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*: uint32 ## Key (predicate) (mapped URI).
|
||||
context*: uint32 ## 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 LV2_Atom.
|
||||
AtomProperty* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: AtomPropertyBody ## Body.
|
||||
|
||||
## The body of an atom:Object. May be cast to LV2_Atom.
|
||||
AtomObjectBody* {.bycopy.} = object
|
||||
id*: uint32 ## URID, or 0 for blank.
|
||||
otype*: uint32 ## URID (same as rdf:type, for fast dispatch).
|
||||
## Contents (a series of property bodies) follow here.
|
||||
|
||||
## An atom:Object. May be cast to LV2_Atom.
|
||||
AtomObject* {.bycopy.} = object
|
||||
atom*: Atom ## Atom header.
|
||||
body*: AtomObjectBody ## Body.
|
||||
|
||||
## The header of an atom:Event. Note this is NOT an LV2_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 atom:Sequence (a sequence of events).
|
||||
##
|
||||
## The unit field is either a URID that described an appropriate time stamp
|
||||
## type, or may be 0 where a default stamp is known. For
|
||||
## LV2_Descriptor::run(), the default stamp is audio frames.
|
||||
##
|
||||
## The contents of a sequence is a series of LV2_Atom_Event, each aligned
|
||||
## to 64-bits, for example:
|
||||
## <pre>
|
||||
## | Event 1 (size 6) | Event 2
|
||||
## | | | | | | | | |
|
||||
## | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|
||||
## |FRAMES |SIZE |TYPE |DATADATADATAPAD|FRAMES |...
|
||||
## </pre>
|
||||
##
|
||||
AtomSequenceBody* {.bycopy.} = object
|
||||
unit*: uint32 ## 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,182 @@
|
|||
## Copyright 2008-2015 David Robillard <d@drobilla.net>
|
||||
## SPDX-License-Identifier: ISC
|
||||
##
|
||||
## Helper functions for the LV2 Atom extension.
|
||||
##
|
||||
## Note these functions are all static inline, do not take their address.
|
||||
##
|
||||
## This header is non-normative, it is provided for convenience.
|
||||
##
|
||||
## Utilities for working with atoms.
|
||||
##
|
||||
|
||||
from system/ansi_c import c_memcmp, c_memcpy
|
||||
import ../atom
|
||||
|
||||
|
||||
##
|
||||
## 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)))
|
||||
|
||||
##
|
||||
## 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 == nil or (atom.`type` == 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
|
||||
c_memcmp(a + 1, b + 1, a.size) == 0)
|
||||
|
||||
##
|
||||
## Sequence Iterator
|
||||
##
|
||||
## Get an iterator pointing to the first event in a Sequence body.
|
||||
##
|
||||
proc atomSequenceBegin*(body: ptr AtomSequenceBody): ptr AtomEvent {.inline.} =
|
||||
return cast[ptr AtomEvent](body + 1)
|
||||
|
||||
|
||||
##
|
||||
## Get an iterator pointing to the end of a Sequence body.
|
||||
##
|
||||
proc atomSequenceEnd*(body: ptr AtomSequenceBody; size: uint32): ptr AtomEvent {.inline.} =
|
||||
return cast[ptr AtomEvent](cast[ptr uint8](body) + atomPadSize(size))
|
||||
|
||||
##
|
||||
## Return true iff `i` has reached the end of `body`.
|
||||
##
|
||||
proc atomSequenceIsEnd*(body: ptr AtomSequenceBody; size: Natural; i: ptr AtomEvent): bool {.inline.} =
|
||||
return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint)
|
||||
|
||||
##
|
||||
## Return an iterator to the element following `i`.
|
||||
##
|
||||
proc atomSequenceNext*(i: ptr AtomEvent): ptr AtomEvent {.inline.} =
|
||||
return cast[ptr AtomEvent](i + sizeof(AtomEvent) + atomPadSize(i.body.size))
|
||||
|
||||
##
|
||||
## An iterator for looping over all events in a Sequence.
|
||||
## @param seq 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)
|
||||
|
||||
## TODO: Like LV2_ATOM_SEQUENCE_FOREACH but for a headerless sequence body.
|
||||
|
||||
##
|
||||
## Sequence Utilities
|
||||
##
|
||||
## Clear all events from `sequence`.
|
||||
##
|
||||
## This simply resets the size field, the other fields are left untouched.
|
||||
##
|
||||
proc atomSequenceClear*(seq: ptr AtomSequence) {.inline.} =
|
||||
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 NULL 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)
|
||||
c_memcpy(e, event, totalSize)
|
||||
seq.atom.size += atomPadSize(totalSize)
|
||||
return e
|
||||
|
||||
##
|
||||
## Tuple Iterator
|
||||
##
|
||||
## Get an iterator pointing to the first element in `tup`.
|
||||
##
|
||||
proc atomTupleBegin*(tup: ptr AtomTuple): ptr Atom {.inline.} =
|
||||
return cast[ptr Atom](atomContents(Atom, tup))
|
||||
|
||||
##
|
||||
## Return true iff `i` has reached the end of `body`.
|
||||
##
|
||||
proc atomTupleIsEnd*(body: pointer; size: Natural; i: ptr Atom): bool {.inline.} =
|
||||
return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.int)
|
||||
|
||||
##
|
||||
## Return an iterator to the element following `i`.
|
||||
##
|
||||
proc atomTupleNext*(i: ptr Atom): ptr Atom {.inline.} =
|
||||
return cast[ptr Atom](cast[ptr uint8](i) + sizeof(Atom) + atomPadSize(i.size))
|
||||
|
||||
|
||||
## TODO:
|
||||
##
|
||||
## A iterator for looping over all properties of a Tuple.
|
||||
## @param tuple The tuple to iterate over
|
||||
|
||||
## TODO:
|
||||
## Like LV2_ATOM_TUPLE_FOREACH but for a headerless tuple body.
|
||||
|
||||
##
|
||||
## Object Iterator
|
||||
##
|
||||
## Return a pointer to the first property in `body`.
|
||||
##
|
||||
proc atomObjectBegin*(body: ptr AtomObjectBody): ptr AtomPropertyBody {.inline.} =
|
||||
return cast[ptr AtomPropertyBody](body + 1)
|
||||
|
||||
##
|
||||
## Return true iff `i` has reached the end of `obj`.
|
||||
##
|
||||
proc atomObjectIsEnd*(body: ptr AtomObjectBody; size: Natural; i: ptr AtomPropertyBody): bool {.inline.} =
|
||||
return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.int)
|
||||
|
||||
##
|
||||
## Return an iterator to the property following `i`.
|
||||
##
|
||||
proc atomObjectNext*(i: ptr AtomPropertyBody): ptr AtomPropertyBody {.inline.} =
|
||||
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,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,100 @@
|
|||
## 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"
|
||||
|
||||
##
|
||||
## Opaque pointer to host data for uridMap.
|
||||
##
|
||||
|
||||
type UridMapHandle* = pointer
|
||||
|
||||
##
|
||||
## Opaque pointer to host data for uridUnmap.
|
||||
##
|
||||
|
||||
type UridUnmapHandle* = pointer
|
||||
|
||||
##
|
||||
## URI mapped to an integer.
|
||||
##
|
||||
|
||||
type Urid* = distinct uint32
|
||||
|
||||
##
|
||||
## URID Map Feature (LV2_URID__map)
|
||||
##
|
||||
|
||||
type 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 (LV2_URID__unmap)
|
||||
##
|
||||
|
||||
type 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 ../nymph
|
||||
|
||||
## 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 features != nil:
|
||||
var i = 0
|
||||
while true:
|
||||
let feature = features[i]
|
||||
if feature == nil:
|
||||
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