 
	The design goal of the plugin bundle was to create a tool to easily add realtime programmable logic glue in LV2 plugin graphs.
To have plugins which do a specific task efficiently is great, especially for audio plugins. LV2 stands apart from other audio plugin specifications with its extentable event system based on Atoms. As events can be much more varied in nature and represent pretty much anything (NOT ONLY MIDI), it would be useful to have a tool to create arbitrary event filters for a given setup on-the-fly.
For a given setup, one may need a special event filter only once and it seems to be overkill to write a native LV2 event filter in C/C++ just for that. It would also be nice to have a tool for fast prototyping of new event filters.
A scripting language seems to be ideal for these cases, where the user can write an event filter on a higher level of abstraction on-the-fly. The scripting language needs to be realtime safe, though, which restricts the choices dramatically.
One such scripting language is Lua. It is small, fast, easily embeddable and realtime-safe if coupled to a realtime-safe memory allocator like TLSF.
The Moony plugins can handle LV2 control and atom event ports, only. They do not handle LV2 audio ports. Control port values are internally handled as simple floating point numbers, whereas the atom event ports build on top of the LV2 atom and atom forge C headers.
The Moony plugin bundle ships with multiple plugin variants, whereas all of them share the same API but with different configurations of input and output ports. Depending on the users's usage scenario, one or the other variant may be preferable.
Plugin variants only differ in number of control and atom event input and output ports. Apart from this difference, they can often be used interchangeably.
This plugin variant features a single control port input and a single control port output.
-- C1 x C1 prototype 'run' function
function run(n, control, notify, c)
  return c
endThis plugin variant features two control port inputs and two control port outputs.
-- C2 x C2 prototype 'run' function
function run(n, control, notify, c1, c2)
  return c1, c2
endThis plugin variant features four control port inputs and four control port outputs.
-- C4 x C4 prototype 'run' function
function run(n, control, notify, c1, c2, c3, c4)
  return c1, c2, c3, c4
endThis plugin variant features a single atom port input and a single atom port output.
-- A1 x A1 prototype 'run' function
function run(n, control, notify, seq, forge)
endThis plugin variant features two atom port inputs and two atom port outputs.
-- A2 x A2 prototype 'run' function
function run(n, control, notify, seq1, forge1, seq2, forge2)
endThis plugin variant features four atom port inputs and four atom port outputs.
-- A4 x A4 prototype 'run' function
function run(n, control, notify, seq1, forge1, seq2, forge2, seq3, forge3, seq4, forge4)
endThis plugin variant features a single control port input and a single control port output with a single atom port input and a single atom port output.
-- C1+A1 x C1+A1 prototype 'run' function
function run(n, control, notify, seq, forge, c)
  return c
endThis plugin variant features two control port inputs and two control port outputs with a single atom port input and a single atom port output.
-- C2+A1 x C2+A1 prototype 'run' function
function run(n, control, notify, seq, forge, c1, c2)
  return c1, c2
endThis plugin variant features four control port inputs and four control port outputs with a single atom port input and a single atom port output.
-- C4+A1 x C4+A1 prototype 'run' function
function run(n, control, notify, seq, forge, c1, c2, c3, c4)
  return c1, c2, c3, c4
endMoony runs its Lua interpreter in sandboxed mode, e.g. because of its real-time requirements, Moony cannot provide any access to blocking functions like file I/O and pipes. Only the following standard Lua libraries are enabled:
The following standard Lua libraries are disabled:
The following base functions are disabled:
The following math functions are disabled (use the dedicated random utility library instead):
Additionally to the standard libraries, Moony ships the following commonly used utility libraries:
Whenever you want to log or debug something while developing your scripts, you can easily dump any value via Lua's print function. The print's output will be shown on the UI and also be sent to the host's log backend, e.g. to a log window or console.
-- sends 'hello world' to the UI and the host's log backend
print('hello world')Moony can run user defined callbacks at different positions in its processing graph. If the user provides an implementation for a specific callback, Moony will run the latter. If the plugin does not need a specific callback, the latter can simply be omitted. The implementations of all callbacks are thus optional.
The run callback function is the main callback function and thus the most important one. It is called once per period by the plugin host. The period size, e.g. the number of audio samples the function is called for may be chosen arbitrary by the host. Do not expect it to be constant.
Depending on the plugin variant, the run function takes variable amount of arguments. All variants have the first and last two arguments in common, though.
If a plugin has input event ports, sequence objects (Lua userdata) will be given to the run function whose events can be iterated over.
If a plugin has output event ports, forge objects (Lua userdata) will be given to the run function which can be filled with arbitrary atom event structures.
If a plugin has input control ports, those values will be given to the run function as native Lua numbers.
If a plugin has output control ports, the run function is expected to return native Lua numbers, missing return values will be set to 0.0.
-- 'run' callback prototype for moony#a1xa1
function run(n, control, notify, seq, forge)
  -- here we will process events
endThe once function exists for pure convenience only. It has exactly the same form as the run function, but is called only once after the whole script code is loaded or updated.
The once function will run directly before the run function, it therefore is no replacement for the latter in a given update period, but and extension to it.
Put logic here that needs to run once after script code update, e.g. registering a new StateResponder.
-- 'once' callback prototype for moony#a1xa1
function once(n, control, notify, seq, forge)
  -- here we will run logic once right after a code update
  -- send all-notes-off MIDI message on all channels
  for channel = 0x0, 0xf do
    forge:time(0):midi(MIDI.Controller | channel, MIDI.AllNotesOff)
  end
endWhen script code is reloaded, plugin state inside Lua virtual machine potentially is lost. In order to preserve state across a script code reload, arbitrary state can be serialized and parked in temporary memory. Such a temporary serialized state can later be deserialized via apply.
The stash function is directly called before switching to the new script code.
-- 'stash' callback prototype
-- a simple flag
local myflag = true
function stash(forge)
  -- serialize single boolean flag
  forge:bool(myflag)
end
function apply(atom)
  assert(atom.type == Atom.Bool)
  -- deserialize single boolean flag
  myflag = atom.body
  assert(myflag == true)
endWhen script code is reloaded, plugin state inside Lua virtual machine potentially is lost. In order to preserve state across a script code reload, arbitrary state previously serialized by stash can be deserialized from temporary memory.
The apply function is directly called after switching to the new script code.
-- 'apply' callback prototype
-- table that holds active notes
local active_notes = {
  60, 64, 67, 72
}
function stash(forge)
  local tup = forge:tuple()
  -- serialize active notes to atom tuple
  for i, v in ipairs(active_notes) do
    tup:int(v)
  end
  tup:pop()
end
function apply(atom)
  assert(atom.type == Atom.Tuple)
  -- clear active notes table
  active_notes = {}
  -- deserialize active notes from atom tuple
  for i, v in atom:foreach() do
    assert(v.type == Atom.Int)
    active_notes[i] = v.body
  end
  assert(#active_notes == 4)
endThe save function works analogously to stash, but instead of serializing to temporary memory before a script code reload, save serializes to disk upon a state save request issued by the host.
State serialized to disk by save is preserved across plugin instantiations and truly is the same as a preset.
-- 'save' callback prototype
local urn = Mapper('urn:uuid:6f7e2445-ae5f-4e2e-ae11-9a5cfb1b5d1e#')
-- state
local state = {
  [urn.foo] = {
    [RDFS.range] = Atom.Int,
    [RDF.value] = 12
  },
  [urn.bar] = {
    [RDFS.range] = Atom.Chunk,
    [RDF.value] = string.char(0x0, 0x1, 0x2)
  }
}
function save(forge)
  local obj = forge:object()
  -- serialize state to atom object
  for k, v in pairs(state) do
    obj:key(k):typed(v[RDFS.range], v[RDF.value])
  end
  obj:pop()
end
function restore(atom)
  assert(atom.type == Atom.Object)
  -- deserialize state from atom object
  for k, v in pairs(state) do
    local itm = atom[k]
    if itm then
      assert(itm.type == v[RDFS.range])
      assert(itm.body == v[RDF.value])
      v[RDF.value] = itm.body
    end
  end
endThe restore function works analogously to apply, but instead of deserializing from temporary memory after a script code reload, restore deserializes from disk upon a state reload request issued by the host.
If defined, the function is called upon each code reload with the most recently saved state directly after apply and before once or run.
-- 'restore' callback prototype
local urn = Mapper('urn:uuid:8fdd61cd-9c12-4366-b208-8306b37cb981#')
-- 3D positional state
local state = {
  [urn.position] = { 0.2, 0.3, 0.9 },
  [urn.rotation] = { -math.pi, 0.0, math.pi/2 }
}
function save(forge)
  local obj = forge:object()
  -- serialize state to atom object
  for k, v in pairs(state) do
    for vec in obj:key(k):vector(Atom.Float):autopop() do
      for i, w in ipairs(v) do
        vec:float(w)
      end
    end
  end
  obj:pop()
end
function restore(atom)
  assert(atom.type == Atom.Object)
  -- deserialize state from atom object
  for k, v in pairs(state) do
    local itm = atom[k]
    if itm then
      assert(itm.type == Atom.Vector)
      assert(itm.childType == Atom.Float)
      state[k] = { itm:unpack() }
      assert(#state[k] == 3)
    end
  end
endLV2 references objects and their properties by URIs, usually rather long, unique string identifiers. When comparing URIs, whole strings need to be compared, which is inefficient. In LV2, an URI thus can be mapped to a simple integer URID, greatly increasing efficiency of comparisons. Unmapping from URID to original URI is supported, too.
Moony directly mirrors mapping and unmapping of the underlying low-level implementation, but additionally caches every newly queried URI or URID for faster lookup inside script code. Cached URIs and URIDs are not preserved across script code updates, though.
Map an URI to its corresponding URID. Map is a first-class Lua table acting as a cache for all queried URIDs and can also be called as function.
-- Map can be indexed or called as function
local prefix = 'http://example.com#'
local foo_urid = Map(prefix .. 'foo')
local bar_urid = Map[prefix .. 'bar']Unmap an URID to its original URI. Unmap is a first-class Lua table acting as a cache for all queried URIs and can also be called as function.
-- Unmap can be indexed or called as function
local prefix = 'http://example.com#'
local foo_urid= Map(prefix .. 'foo')
local bar_urid = Map[prefix .. 'bar']
local foo_uri = Unmap(foo_urid)
local bar_uri = Unmap[bar_urid]
assert(prefix .. 'foo' == foo_uri)
assert(prefix .. 'bar' == bar_uri)A convenience wrapper and syntactic sugar around Map. Simplifies creating multiple mappings which share a common URI prefix.
Returns a table which can be queried by indexing with the URI postfix. The query is equivalent to a mapping of concatenated URI prefix and postfix.
This is especially useful when dealing with custom properties and the StateResponder.
-- Mapper is a convenience wrapper over Map
local prefix = 'urn:uuid:d178f397-3179-4c38-9d25-31d60ffc5788#'
local mapper = Mapper(prefix)
local bar_urid = Map[prefix .. 'bar']
assert(mapper.bar == bar_urid)Returns a unique, non-colliding integer ID to be used in event messaging. Especially useful for Patch messages or custom event systems where there is a need to track event properties over space and time.
-- Blank returns unique integer IDs
local cache = {}
for i = 1, 100 do
  local id = Blank()
  assert(not cache[id])
  cache[id] = true
endA forge object is used to serialize arbitrary complex atoms, both simple primitives and nested containers.
Atom types that contain single data as part of their body are referred to as primitives.
Forge a boolean atom, e.g. of type Atom.Bool.
-- Forge Bool
function stash_tuple(forge)
  forge:bool(true)
  forge:bool(false)
endForge an integer atom, e.g. of type Atom.Int.
-- Forge Int
function stash(forge)
  forge:int(12)
endForge long integer atom, e.g. of type Atom.Long.
-- Forge Long
function stash(forge)
  forge:long(12)
endForge single-precision float atom, e.g. of type Atom.Float.
-- Forge Float
function stash(forge)
  forge:float(12.5)
endForge double-precision float atom, e.g. of type Atom.Double.
-- Forge Double
function stash(forge)
  forge:double(12.5)
endForge URID atom, e.g. of type Atom.URID.
-- Forge URID
local uri = 'urn:uuid:887c5b2e-89f9-4f1d-aa7c-0ac240ea11b5#foo'
local urid = Map[uri]
function stash(forge)
  forge:urid(urid)
endForge URI atom, e.g. of type Atom.URI.
-- Forge URI
local uri = 'urn:uuid:1f2eb75a-29dc-446b-a8eb-c22d20144a85#foo'
function stash(forge)
  forge:uri(uri)
endForge string atom, e.g. of type Atom.String.
-- Forge String
function stash(forge)
  forge:string('hello world')
endForge path atom, e.g. of type Atom.Path.
-- Forge Path
function stash(forge)
  forge:path('/tmp/xyz')
endForge literal atom, e.g. of type Atom.Literal.
-- Forge Literal
local rus = Map['http://lexvo.org/id/iso639-3/rus'] -- Russian language
function stash_tuple(forge)
  forge:literal('902A7F', MIDI.MidiEvent) -- MIDI encoded as hexidecimal text
  forge:literal('Приве́т!', nil, rus) -- 'hello!'
endForge chunk atom, e.g. of type Atom.Chunk.
-- Forge Chunk
function stash_tuple(forge)
  forge:chunk(string.char(0x1, 0x2, 0x3))
  forge:chunk({0x1, 0x2, 0x3})
  forge:chunk(0x1, 0x2, 0x3)
endForge MIDI atom, e.g. of type MIDI.MidiEvent.
-- Forge MIDI
function stash_tuple(forge)
  forge:midi(string.char(MIDI.NoteOn, 69, 0x7f))
  forge:midi({MIDI.NoteOn, 69, 0x7f})
  forge:midi(MIDI.NoteOn, 69, 0x7f)
endForge a custom atom of arbitrary type.
-- Forge Raw
local byt = { -- stores individual bytes of zero-terminated string 'LV2'
  string.byte('L'),
  string.byte('V'),
  string.byte('2'),
  string.byte('\0')
}
function stash_tuple(forge)
  forge:raw(Atom.String, string.char(table.unpack(byt)))
  forge:raw(Atom.String, byt)
  forge:raw(Atom.String, table.unpack(byt))
endForge an atom of arbitrary type.
-- Forge Typed
function stash_tuple(forge)
  forge:typed(Atom.Int, 12) -- equivalent to forge:int(...)
  forge:typed(Atom.Float, 12.5) -- equivalent to forge:float(...)
  forge:typed(Atom.String, 'LV2 rocks!') -- equivalent to forge:string(...)
endForge an unchanged atom. Useful for cloning whole atoms.
-- Forge Atom
-- forge integer atom
function stash_tuple(forge)
  forge:int(12)
end
-- forge prepared integer atom in whole
function apply_tuple(tup, forge)
  assert(tup[1].body == 12)
  forge:atom(tup[1])
end
-- check if integer atom has been forged in whole
function check(tup)
  assert(tup[1].body == 12)
endAtom types that contain nested atoms as part of their body are referred to as containers.
Forge a sequence atom, e.g. an atom of type Atom.Sequence.
-- Forge Sequence
function stash_tuple(forge)
  local seq = forge:sequence() -- create derived container forge object
    seq:time(0):int(12)
    seq:frameTime(1):int(13)
  seq:pop() -- finalize container
  seq = forge:sequence(Atom.frameTime) -- create derived container forge object
    seq:time(0):int(12)
    seq:frameTime(1):int(13)
  seq:pop() -- finalize container
  for seq in forge:sequence(Atom.beatTime):autopop() do -- create iterator for derived container forge object
    seq:time(0.0):int(12)
    seq:beatTime(1.0):int(13)
  end -- finalize container
endForge frame time of event. Use this on sequences with unit 0 or Atom.frameTime.
Forge beat time of event. Use this on sequences with unit Atom.beatTime.
Forge frame or beat time of event. Can be used as syntactic sugar instead of Frame Time or Beat Time.
Forge an object atom, e.g. an atom of type Atom.Object.
-- Forge Object
local urn = Mapper('urn:uuid:efc85bf7-0246-486f-948b-0cfbcae4e1053#')
function stash_tuple(forge)
  local obj = forge:object(urn.FooBar) -- create derived container forge object
    obj:key(urn.foo):int(12) -- without context
    obj:key(urn.bar, urn.ctx):long(13) -- with context
  obj:pop() -- finalize container
  for obj in forge:object(urn.FooBar):autopop() do -- create iterator for derived container forge object
    obj:key(urn.foo):int(12) -- without context
    obj:key(urn.bar, urn.ctx):long(13) -- with context
  end -- finalize container
endForge key of object property.
Forge a tuple atom, e.g. an atom of type Atom.Tuple.
-- Forge Tuple
function stash_tuple(forge)
  local tup = forge:tuple() -- create derived container forge object
    tup:int(12)
    tup:long(13)
    tup:float(13.5)
    tup:double(14.5)
    tup:string('this is an element of an atom tuple')
    tup:bool(true)
  tup:pop() -- finalize container
  for tup in forge:tuple():autopop() do -- create iterator for derived container forge object
    tup:int(12)
    tup:long(13)
    tup:float(13.5)
    tup:double(14.5)
    tup:string('this is an element of an atom tuple')
    tup:bool(true)
  end -- finalize container
endForge a vector atom, e.g. an atom of type Atom.Vector.
-- Forge Vector
local vBool = {
  true, false, true
}
function stash_tuple(forge)
  forge:vector(Atom.Int):int(1):int(2):int(3):int(4):int(5):pop()
  forge:vector(Atom.Int, {1, 2, 3, 4, 5})
  forge:vector(Atom.Int, 1, 2, 3, 4, 5)
  for vec in forge:vector(Atom.Bool):autopop() do
    for i, v in ipairs(vBool) do
      vec:bool(v)
    end
  end
  forge:vector(Atom.Bool, vBool)
  forge:vector(Atom.Bool, table.unpack(vBool))
endFinalize any derived container forge object.
-- Pop
function stash(forge)
  local tup = forge:tuple()
  tup:int(1)
  tup:int(2)
  tup:pop()
end
function apply(atom)
  assert(#atom == 2)
endFinalize any derived container forge object automatically via a for-iterator-construction.
-- Autopop
function stash(forge)
  for tup in forge:tuple():autopop() do
    tup:int(1)
    tup:int(2)
  end
end
function apply(atom)
  assert(#atom == 2)
endAtom types that contain Open Sound Control bundles or messages as part of their body.
Forge a OSC bundle atom, e.g. an atom object of type OSC.Bundle.
-- Forge Bundle
function stash_tuple(forge)
  -- schedule bundle for immediate dispatch
  local bndl = forge:bundle() -- create derived container forge object
    bndl:message('/hello', 's', 'world')
  bndl:pop() -- finalize container
  -- schedule bundle for dispatch @ Sep 19, 2016 02.09.16.015811000 UTC
  bndl = forge:bundle(0xdb89c74c040c3199)
    bndl:message('/hello', 's', 'world')
  bndl:pop()
  -- schedule bundle for dispatch in 0.1s
  bndl = forge:bundle(0.1)
    bndl:message('/hello', 's', 'world')
  bndl:pop()
endForge a OSC message atom, e.g. an atom object of type OSC.Message. Supported OSC argument types are: 'i', 'f', 's', 'b', 'h', 'd', 't', 'm', 'S', 'c', 'r', 'T', 'F', 'N', 'I'
-- Forge Message
function stash(forge)
  -- schedule bundle for immediate dispatch
  local bndl = forge:bundle() -- create derived container forge object
    bndl:message('/hello', 'ifs', 2016, 12.5, 'hello')
    bndl:message('/hello', 'b', string.char(0x1, 0x2, 0x3))
    bndl:message('/hello', 'hdS', 2017, 13.5, Param.sampleRate)
    bndl:message('/hello', 't', 0xdb89c74c040c3199)
    bndl:message('/hello', 'TFNI')
    bndl:message('/hello', 'm', string.char(MIDI.NoteOn, 69, 0x7f))
    bndl:message('/hello', 'cr', string.byte('s'), 0xff00ff00)
  bndl:pop() -- finalize container)
endForge Impulse argument of OSC message.
Forge Char argument of OSC message.
Forge RGBA color argument of OSC message.
Forge Timetag argument of OSC message.
-- Forge custom OSC arguments
function stash(forge)
  for obj in forge:object(OSC.Message):autopop() do
    obj:key(OSC.messagePath):string('/custom')
    for tup in obj:key(OSC.messageArguments):tuple():autopop() do
      -- forge 'i', 'f', 's', 'b' OSC arguments
      tup:int(12) -- 'i'nteger
      tup:float(1.5) -- 'f'loat
      tup:string('foo') -- 's'tring
      tup:chunk(string.char(0x1, 0x2)) -- 'b'lob
      -- forge 'h', 'd', 't' OSC arguments
      tup:long(13) -- 'h'
      tup:double(1.6) -- 'd'ouble
      tup:timetag(0.1) -- 't', 100ms from now
      -- forge 'N', 'I', 'T', 'F' OSC arguments
      tup:raw(0) -- empty atom, 'N'il
      tup:impulse() -- 'I'mpulse
      tup:bool(true) -- 'T'rue
      tup:bool(false) -- 'F'alse
      -- forge 'c', 'r', 'm', 'S' OSC arguments
      tup:char(string.byte('c')) -- 'c'char
      tup:rgba(0xbb0000ff) -- reddish, 'r'gba
      tup:midi(MIDI.NoteOn, Note['C-0'], 64) -- 'm'idi
      tup:urid(Param.sampleRate) -- 'S'ymbol
    end
  end
endAtom types that contain Patch messages as part of their body.
Forge a patch patch atom, e.g. an atom object of type Patch.Patch.
-- Forge Patch
local urn = Mapper('urn:uuid:29f87e4f-fa45-4f91-aa88-767053006a0d#')
function stash(forge)
  local patch= forge:patch(urn.subj, 1002) -- with subject and sequence number
    local rem = patch:remove() -- equivalent to patch:object(Patch.remove)
      rem:key(urn.foo):urid(Patch.wildcard)
      rem:key(urn.bar):urid(Patch.wildcard)
    rem:pop()
    local add = patch:add() -- equivalent to patch:object(Patch.add)
      add:key(urn.foo):int(12)
      add:key(urn.bar):float(12.5)
    add:pop()
  patch:pop()
endForge add property of patch property.
Forge remove property of patch property.
Forge a patch get atom, e.g. an atom object of type Patch.Get.
-- Forge Get
local urn = Mapper('urn:uuid:5b172c1f-9152-4d4e-ad68-84965792b931#')
function stash_tuple(forge)
  forge:get(urn.prop) -- without subject and sequence number
  forge:get(urn.prop, nil, 1001) -- without subject
  forge:get(urn.prop, urn.subj, 1002) -- with subject and sequence number
endForge a patch set atom, e.g. an atom object of type Patch.Set.
-- Forge Set
local urn = Mapper('urn:uuid:29c714c1-0c7b-4434-81d6-82ee8a4d64b8#')
function stash_tuple(forge)
  local set = forge:set(urn.prop) -- without subject and sequence number
    set:int(12)
  set:pop()
  set = forge:set(urn.prop, urn.subj, 1002) -- with subject and sequence number
    set:float(12)
  set:pop()
endForge a patch put atom, e.g. an atom object of type Patch.Put.
-- Forge Put
local urn = Mapper('urn:uuid:dabe2235-2405-46e6-a26c-90dbe34d9bf3#')
function stash_tuple(forge)
  local put = forge:put() -- without subject and sequence number
    put:key(urn.foo):int(12)
    put:key(urn.bar):float(12.5)
  put:pop()
  put= forge:put(urn.subj, 1002) -- with subject and sequence number
    put:key(urn.foo):int(12)
    put:key(urn.bar):float(12.5)
  put:pop()
endForge an ack patch atom, e.g. an atom object of type Patch.Ack.
-- Forge Ack
local urn = Mapper('urn:uuid:f35c0f85-5b7f-4434-912d-8bf982711b30#')
function stash(forge)
  forge:ack(urn.subj, 1002) -- with subject and sequence number
endForge an error patch atom, e.g. an atom object of type Patch.Error.
-- Forge Error
local urn = Mapper('urn:uuid:93ea103c-75ff-47e2-b89b-fc79173bedee#')
function stash(forge)
  forge:error(urn.subj, 1002) -- with subject and sequence number
endForge a delete patch atom, e.g. an atom object of type Patch.Delete.
-- Forge Delete
local urn = Mapper('urn:uuid:cea077fc-b822-4143-b5c5-34c0f7d9f016#')
function stash(forge)
  forge:delete(urn.subj, 1002) -- with sequence number
endForge a copy patch atom, e.g. an atom object of type Patch.Copy.
-- Forge Copy
local urn = Mapper('urn:uuid:3d4de1a6-a5fa-463d-a59c-c7778a6b18e9#')
function stash(forge)
  forge:copy(urn.subj, urn.dest, 1002) -- with sequence number
endForge a move patch atom, e.g. an atom object of type Patch.Move.
-- Forge Move
local urn = Mapper('urn:uuid:6370dea9-a41a-4347-80f0-75d9fcccc28a#')
function stash(forge)
  forge:move(urn.subj, urn.dest, 1002) -- with sequence number
endForge a patch insert atom, e.g. an atom object of type Patch.Insert.
-- Forge Insert
local urn = Mapper('urn:uuid:16e7ed0d-20ee-45ef-8268-ed931a998a4f#')
function stash_tuple(forge)
  local insert = forge:insert() -- without subject and sequence number
    insert:key(urn.foo):int(12)
    insert:key(urn.bar):float(12.5)
  insert:pop()
  insert= forge:insert(urn.subj, 1002) -- with subject and sequence number
    insert:key(urn.foo):int(12)
    insert:key(urn.bar):float(12.5)
  insert:pop()
endAtom types that contain Canvas messages as part of their body. Modeled after the HTML5 Canvas API. But the vector drawing instructions into an atom:Tuple and store it as property Canvas:graph.
Forge an atom object of type Canvas.BeginPath. Manually begin a new path. Not needed for instructions that inherently begin a new path by themeselves: Canvas.MoveTo, Canvas.Arc, Canvas.Rectangle.
-- Forge BeginPath
function stash(ctx)
  ctx:beginPath()
endForge an atom object of type Canvas.ClosePath. Close a previously begun path.
-- Forge ClosePath
function stash(ctx)
  ctx:closePath()
endForge an atom object of type Canvas.Arc. Create an Arc structure.
-- Forge Arc
function stash(ctx)
  ctx:arc(0.5, 0.5, 0.1):stroke()
  ctx:arc(0.5, 0.5, 0.1, math.pi/4, math.pi/2):stroke()
endForge an atom object of type Canvas.CurveTo. Append a spline curve segment to path.
-- Forge CurveTo
function stash(ctx)
  ctx:beginPath():moveTo(0.0, 0.0):curveTo(0.1, 0.1, 0.2, 0.4, 0.3, 0.9):closePath():stroke()
endForge an atom object of type Canvas.LineTo. Append line segment to path.
-- Forge LineTo 
function stash(ctx)
  ctx:beginPath():moveTo(0.0, 0.0):lineTo(1.0, 1.0):closePath():stroke()
endForge an atom object of type Canvas.MoveTo. Move to given point and begin new path.
-- Forge MoveTo
function stash(ctx)
  ctx:moveTo(0.0, 0.0)
endForge an atom object of type Canvas.Rectangle. Create rectangle.
-- Forge Rectangle
function stash(ctx)
  ctx:rectangle(0.1, 0.1, 0.8, 0.8):fill()
endForge an atom object of type Canvas.PolyLine. Create polyline.
-- Forge PolyLine
function stash(ctx)
  ctx:beginPath():polyLine(0.1, 0.1, 0.9, 0.1, 0.5, 0.9):closePath():fill()
endForge an atom object of type Canvas.Style. Set current stroke and fill color.
-- Forge Style
function stash(ctx)
  ctx:rectangle(0.1, 0.1, 0.2, 0.2):style(0xffffffff):fill() -- opaque white
  ctx:rectangle(0.2, 0.2, 0.2, 0.2):style(0xff0000ff):fill() -- opaque red
  ctx:rectangle(0.3, 0.3, 0.2, 0.2):style(0x00ff0080):fill() -- half-transparent green
endForge an atom object of type Canvas.LineWidth. Set current line width.
-- Forge LineWidth
function stash(ctx)
  ctx:rectangle(0.1, 0.1, 0.2, 0.2):lineWidth(0.01):stroke()
  ctx:rectangle(0.2, 0.2, 0.2, 0.2):lineWidth(0.04):stroke()
endForge an atom object of type Canvas.LineDash. Set current line dash style.
-- Forge LineDash
function stash(ctx)
  ctx:rectangle(0.1, 0.1, 0.2, 0.2):lineDash(0.01, 0.01):stroke()
  ctx:rectangle(0.2, 0.2, 0.2, 0.2):lineDash(0.01, 0.04):stroke()
endForge an atom object of type Canvas.LineCap. Set current line cap style.
-- Forge LineCap
local function tri(ctx, x, y, s, cap)
  ctx:moveTo(x, y):lineTo(x + s, y):lineTo(x + s, y + s/2):lineCap(cap):stroke()
end
function stash(ctx)
  ctx:lineWidth(0.05)
  tri(ctx, 0.3, 0.1, 0.4, Canvas.lineCapButt)
  tri(ctx, 0.2, 0.2, 0.4, Canvas.lineCapRound)
  tri(ctx, 0.1, 0.3, 0.4, Canvas.lineCapSquare)
endForge an atom object of type Canvas.LineJoin. Set current line join style.
-- Forge LineJoin
local function tri(ctx, x, y, s, join)
  ctx:moveTo(x, y):lineTo(x + s, y):lineTo(x + s, y + s/2):lineJoin(join):stroke()
end
function stash(ctx)
  ctx:lineWidth(0.05)
  tri(ctx, 0.3, 0.1, 0.4, Canvas.lineJoinMiter)
  tri(ctx, 0.2, 0.2, 0.4, Canvas.lineJoinRound)
  tri(ctx, 0.1, 0.3, 0.4, Canvas.lineJoinBevel)
endForge an atom object of type Canvas.MiterLimit. Set current line join miter limit.
-- Forge MiterLimit
local function tri(ctx, x, y, s, limit)
  ctx:moveTo(x, y):lineTo(x + s, y):lineTo(x + s, y + s/2):lineJoin(Canvas.lineJoinMiter):miterLimit(limit):stroke()
end
function stash(ctx)
  ctx:lineWidth(0.05)
  tri(ctx, 0.3, 0.1, 0.4, 1.0)
  tri(ctx, 0.2, 0.2, 0.4, 2.0)
endForge an atom object of type Canvas.Stroke. Stroke current path.
-- Forge Stroke
function stash(ctx)
  ctx:rectangle(0.0, 0.0, 1.0, 1.0):stroke()
endForge an atom object of type Canvas.Fill. Fill current path.
-- Forge Fill
function stash(ctx)
  ctx:rectangle(0.0, 0.0, 1.0, 1.0):fill()
endForge an atom object of type Canvas.Clip. Set current path as clip mask.
-- Forge Clip
function stash(ctx)
  ctx:save()
  ctx:arc(0.25, 0.25, 0.25):clip()
  ctx:rectangle(0.25, 0.25, 0.5, 0.5):fill()
  ctx:restore()
endForge an atom object of type Canvas.Save. Push/save current state.
-- Forge Save
function stash(ctx)
  ctx:save():translate(0.5, 0.5):rotate(math.pi/4)
  ctx:rectangle(-0.1, -0.1, 0.2, 0.2):stroke()
  ctx:restore()
  ctx:rectangle(0.4, 0.4, 0.2, 0.2):stroke()
endForge an atom object of type Canvas.Restore. Pop/restore previously saved state.
-- Forge Restore
function stash(ctx)
  ctx:save():translate(0.5, 0.5):rotate(math.pi/4)
  ctx:rectangle(-0.1, -0.1, 0.2, 0.2):stroke()
  ctx:restore()
  ctx:rectangle(0.4, 0.4, 0.2, 0.2):stroke()
endForge an atom object of type Canvas.Translate. Translate current render matrix.
-- Forge Translate
function stash(ctx)
  ctx:save():translate(0.4, 0.4)
  ctx:rectangle(0.0, 0.0, 0.2, 0.2):fill()
  ctx:restore()
endForge an atom object of type Canvas.Scale. Scale current render matrix.
-- Forge Scale
function stash(ctx)
  ctx:save():scale(2.0, 2.0)
  ctx:rectangle(0.0, 0.0, 0.5, 0.5):fill()
  ctx:restore()
endForge an atom object of type Canvas.Rotate. Rotate current render matrix.
-- Forge Rotate
function stash(ctx)
  ctx:save():translate(0.4, 0.4):rotate(math.pi/4) -- rotate by 45 degrees
  ctx:rectangle(-0.1, -0.1, 0.2, 0.2):fill()
  ctx:restore()
endForge an atom object of type Canvas.Transform. Set current render matrix.
-- Forge Transform
function stash(ctx)
  ctx:save():transform(-1.0, 0.0, 1.0, 1.0, 0.0, 0.0) -- mirror on x-asix
  ctx:rectangle(-0.1, -0.1, 0.2, 0.2):fill()
  ctx:restore()
endForge an atom object of type Canvas.Reset. Reset current render matrix to identity configuration.
-- Forge Reset
function stash(ctx)
  ctx:translate(0.4, 0.4)
  ctx:rectangle(0.0, 0.0, 0.2, 0.2):fill()
  ctx:reset()
  ctx:rectangle(0.0, 0.0, 0.2, 0.2):fill()
endForge an atom object of type Canvas.FontSize. Set font size.
-- Forge FontSize
function stash(ctx)
  ctx:moveTo(0.5, 0.5):fontSize(0.1):fillText('Hello')
endForge an atom object of type Canvas.FillText. Fill/draw text at current position.
-- Forge FillText
function stash(ctx)
  ctx:moveTo(0.5, 0.5):fontSize(0.1):style(0xff0000ff):fillText('Hello')
endInstead of deserializing all LV2 event data to corresponding Lua types, Moony instead presents a proxy object to the user in form of a Lua userdata. This is needed because there are more LV2 atom types than native Lua types. It turns out to be more efficient, too, as the user usually wants to filter events before looking at their payload.
All atom types have some common attributes they can be queried for:
Atom types that contain single data as part of their body are referred to as primitives.
An empty atom, e.g. of type and size 0.
-- Atom Nil
-- serialize
function stash(forge)
end
function apply(atom)
  -- attributes
  assert(#atom == 0) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == 0) -- query type of atom
  assert(atom.body == nil) -- get atom body
endA boolean atom, e.g. of type Atom.Bool and size 4.
-- Atom Bool
-- serialize
function stash(forge)
  forge:bool(true)
end
function apply(atom)
  -- attributes
  assert(#atom == 4) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Bool) -- query type of atom
  assert(atom.body == true) -- get atom body
endAn integer atom, e.g. of type Atom.Int and size 4.
-- Atom Int
-- serialize
function stash(forge)
  forge:int(12)
end
function apply(atom)
  -- attributes
  assert(#atom == 4) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Int) -- query type of atom
  assert(atom.body == 12) -- get atom body
endA long integer atom, e.g. of type Atom.Long and size 8.
-- Atom Long
-- serialize
function stash(forge)
  forge:long(13)
end
function apply(atom)
  -- attributes
  assert(#atom == 8) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Long) -- query type of atom
  assert(atom.body == 13) -- get atom body
endA single-precision float atom, e.g. of type Atom.Float and size 4.
-- Atom Float
-- serialize
function stash(forge)
  forge:float(1.5)
end
function apply(atom)
  -- attributes
  assert(#atom == 4) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Float) -- query type of atom
  assert(atom.body == 1.5) -- get atom body
endA double-precision float atom, e.g. of type Atom.Double and size 8.
-- Atom Double
-- serialize
function stash(forge)
  forge:double(1.6)
end
function apply(atom)
  -- attributes
  assert(#atom == 8) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Double) -- query type of atom
  assert(atom.body == 1.6) -- get atom body
endAn URID atom, e.g. of type Atom.URID and size 4.
-- Atom URID
local urid = Map['urn:uuid:6d82e244-ee66-403f-aea1-26b3d9823820#foo']
-- serialize
function stash(forge)
  forge:urid(urid)
end
function apply(atom)
  -- attributes
  assert(#atom == 4) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.URID) -- query type of atom
  assert(atom.body == urid) -- get atom body
endAn URI atom, e.g. of type Atom.URI and variable size. Atom size denotes to string size plus zero byte terminator.
-- Atom URI
local uri = 'urn:uuid:022ec18a-0a02-4a19-ad7a-a10403a0c4c3#foo'
-- serialize
function stash(forge)
  forge:uri(uri)
end
function apply(atom)
  -- attributes
  assert(#atom == #uri + 1) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.URI) -- query type of atom
  assert(atom.body == uri) -- get atom body
endA string atom, e.g. of type Atom.String and variable size. Atom size denotes to string size plus zero byte terminator.
-- Atom String
local str = 'hello world'
-- serialize
function stash(forge)
  forge:string(str)
end
function apply(atom)
  -- attributes
  assert(#atom == #str + 1) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.String) -- query type of atom
  assert(atom.body == str) -- get atom body
endA path atom, e.g. of type Atom.Path and variable size. Atom size denotes to string size plus zero byte terminator.
-- Atom Path
local tmp = '/tmp/xyz'
-- serialize
function stash(forge)
  forge:path(tmp)
end
function apply(atom)
  -- attributes
  assert(#atom == #tmp + 1) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Path) -- query type of atom
  assert(atom.body == tmp) -- get atom body
endA literal atom, e.g. of type Atom.Literal and variable size. Atom size denotes to string size plus zero byte terminator. Atom literals may have either a data type or language attribute.
-- Atom  Literal
local lit = 'Hallo Welt'
local urn = Mapper('urn:uuid:c2f619db-3eef-411c-82c9-ffe0df1c10fc3')
local datatype = urn.datatype
local lang = urn.lang
-- serialize
function stash(forge)
  forge:literal(lit, datatype, lang)
end
function apply(atom)
  -- attributes
  assert(#atom == 4 + 4 + #lit + 1) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Literal) -- query type of atom
  assert(atom.datatype == datatype) -- query datatype of literal
  assert(atom.lang == lang) -- query datatype of literal
  assert(atom.body == lit) -- get atom body
  -- unpacking all attributes
  local lit2, datatype2, lang2 = atom:unpack()
  assert(lit2 == lit)
  assert(datatype2 == datatype)
  assert(lang2 == lang)
endA chunk atom, e.g. of type Atom.Chunk and variable size. Atom size denotes to byte size of atom body.
-- Atom Chunk
local byt = string.char(0x1, 0x2, 0x3)
-- serialize
function stash(forge)
  forge:chunk(byt)
end
function apply(atom)
  -- attributes
  assert(#atom == #byt) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == Atom.Chunk) -- query type of atom
  assert(atom.body == byt) -- get atom body
  -- unpacking all bytes
  local byt1, byt2, byt3 = atom:unpack() -- equivalent to atom:unpack(1, #atom)
  assert(byt1 == 0x1)
  assert(byt2 == 0x2)
  assert(byt3 == 0x3)
  -- indexing individual bytes
  assert(atom[1] == 0x1)
  assert(atom[2] == 0x2)
  assert(atom[3] == 0x3)
endA MIDI atom, e.g. of type MIDI.MidiEvent and variable size. Atom size denotes to byte size of atom body. A MIDI atom is an equivalent to a Chunk atom and thus features the same class attributes and methods.
-- Atom MIDI
local byt = string.char(MIDI.NoteOn, 69, 64)
-- serialize
function stash(forge)
  forge:midi(byt)
end
function apply(atom)
  -- attributes
  assert(#atom == #byt) -- query size of atom body
  print(tostring(atom)) -- convert to string
  assert(atom.type == MIDI.MidiEvent) -- query type of atom
  assert(atom.body == byt) -- get atom body
  -- unpack all bytes
  local byt1, byt2, byt3 = atom:unpack() -- equivalent to atom:unpack(1, #atom)
  assert(byt1 == MIDI.NoteOn)
  assert(byt2 == 69)
  assert(byt3 == 64)
  -- indexing individual bytes
  assert(atom[1] == MIDI.NoteOn)
  assert(atom[2] == 69)
  assert(atom[3] == 64)
endAtom types that contain nested atoms as part of their body are referred to as containers.
A Sequence atom, e.g. of type Atom.Sequence and variable size. Atom size denotes to number of events contained in atom body. An atom sequence consists of individual atom events, each with a time stamp and event pyaload.
-- Atom Sequence
-- serialize
function stash(forge)
  local seq = forge:sequence(Atom.frameTime)
    seq:time(0):int(12)
  seq:pop()
end
function apply(seq)
  -- attributes
  assert(#seq == 1) -- query number of events
  print(tostring(seq)) -- convert to string
  assert(seq.type == Atom.Sequence) -- query type of atom
  assert(seq.unit == Atom.frameTime) -- query event time unit
  -- iterate over
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.body == 12)
  end
  -- indexing
  local atom = seq[1]
  assert(atom.body == 12)
endA Tuple atom, e.g. of type Atom.Tuple and variable size. Atom size denotes to number of items contained in atom body. An atom tuple consists of individual atom items.
-- Atom Tuple
-- serialize
function stash(forge)
  local tup = forge:tuple()
    tup:int(12)
  tup:pop()
end
function apply(tup)
  -- attributes
  assert(#tup == 1) -- query number of items
  print(tostring(tup)) -- convert to string
  assert(tup.type == Atom.Tuple) -- query type of atom
  -- iterate over
  for i, atom in tup:foreach() do
    assert(i == 1)
    assert(atom.body == 12)
  end
  -- unpack
  local a1 = tup:unpack()
  assert(a1.body == 12)
  -- indexing
  a1 = tup[1]
  assert(a1.body == 12)
endAn Object atom, e.g. of type Atom.Object and variable size. Atom size denotes to number of properties contained in atom body. An atom object consists of individual properties.
-- Atom Object
local urn = Mapper('urn:uuid:b110d31d-98c6-48bc-93d1-e326c9829be9#')
-- serialize
function stash(forge)
  local obj = forge:object(urn.otype, urn.id)
    obj:key(urn.key):int(12)
  obj:pop()
end
function apply(obj)
  -- attributes
  assert(#obj == 1) -- query number of properties
  print(tostring(obj)) -- convert to string
  assert(obj.type == Atom.Object) -- query type of atom
  assert(obj.id == urn.id) -- query id of object atom
  assert(obj.otype == urn.otype) -- query type of object atom
  -- iterate over
  for k, atom, ctx in obj:foreach() do
    assert(k == urn.key)
    assert(atom.body == 12)
    assert(ctx == 0)
  end
  -- indexing
  local atom = obj[urn.key]
  assert(atom.body == 12)
endA Vector atom, e.g. of type Atom.Vector and variable size. Atom size denotes to number of items contained in atom body. An atom vector consists of equally typed atom items.
-- Atom Vector
-- serialize
function stash(forge)
  forge:vector(Atom.Int):int(12):int(13):int(14):pop()
end
function apply(vec)
  -- attributes
  assert(#vec == 3) -- query number of items
  print(tostring(vec)) -- convert to string
  assert(vec.type == Atom.Vector) -- query type of atom
  assert(vec.childSize == 4) -- query child size of atom
  assert(vec.childType == Atom.Int) -- query child type of atom
  -- iterate over
  for i, atom in vec:foreach() do
    if i == 1 then
      assert(atom.body == 12)
    elseif i == 2 then
      assert(atom.body == 13)
    elseif i == 3 then
      assert(atom.body == 14)
    end
  end
  -- unpack
  local v1, v2, v3 = vec:unpack()
  assert(v1.body == 12)
  assert(v2.body == 13)
  assert(v3.body == 14)
  -- indexing
  v1, v2, v3 = vec[1], vec[2], vec[3]
  assert(v1.body == 12)
  assert(v2.body == 13)
  assert(v3.body == 14)
endSometimes it may be useful to not only be able to serialize atoms to forge objects provided to the user via one of the callback functions, but to be able to temporarily serialize some atoms to memory for later dispatch.
For these usage scenarios there is the stash object, which according to its name, functions as temporary stash. It is a special object in the way that it can either be an atom object (with all its attributes and methods) or a forge object (with all its methods), depending on whether it is in its reading or writing mode.
After creating a new stash, it is in its writing mode and thus can be used just like a forge object. After finishing the serialization procedure, the stash object may be switched into its reading mode and be used just like an atom object.
-- Stash
-- a new stash defaults to writing mode - equivalent to forge object
local io = Stash()
io:int(12)
-- switch to reading mode - equivalent to atom object
io:read()
assert(io.body == 12)
-- switch to writing mode - automatically clears previous content
io:write()
io:int(13)
-- switch to reading mode
io:read()
assert(io.body == 13)Sometimes it is useful to know the sample rate (e.g. for timing) and size of sequence buffers and minimal and maximal to expect audio block frames. If the host exports those values, they may be queried via the options table.
-- Options
-- only available if exported by host
local sampleRate = Options[Param.sampleRate]
assert(sampleRate.body == 48000)
local sequenceSize = Options[Buf_Size.sequenceSize]
local minBlockLength = Options[Buf_Size.minBlockLength]
local maxBlockLength = Options[Buf_Size.maxBlockLength]
local updateRate = Options(Ui.updateRate)
  
for urid, atom in pairs(Options) do
  print(Unmap[urid], atom)
endResponders are convenience wrappers to ease development of frequently used logic event handling schemes via callbacks.
Moony offers simple responders for MIDI and OSC handling and more complex responders for time and state handling.
By using responder objects, common problems like event filtering and sequencing can be written with much less and more understandable code.
The StateResponder can be used to build simple user interfaces to make any moony script available to non-coders around the world.
Runs callbacks for received MIDI messages.
-- MIDIResponder
-- define MIDI responder object with callbacks
local midiR = MIDIResponder({
  -- listen for NoteOn messages
  [MIDI.NoteOn] = function(self, frames, forge, chan, note, vel)
    assert(frames == 0)
    assert(chan == 0x1)
    assert(note == 69)
    assert(vel == 0x7f)
    -- send NoteOn message
    forge:time(frames):midi(MIDI.NoteOn | chan, note + 1, vel) -- increase note by 1
  end,
  -- listen for NoteOff messages
  [MIDI.NoteOff] = function(self, frames, forge, chan, note, vel)
    assert(frames == 1)
    assert(chan == 0x1)
    assert(note == 69)
    assert(vel == 0x0)
    -- send NoteOff message
    forge:time(frames):midi(MIDI.NoteOff | chan, note + 1, vel) -- increase note by 1
  end
}, false) -- block all MIDI messages not explicitly handled by responder
-- forge test messages
function stash_sequence(forge)
  forge:time(0):midi(MIDI.NoteOn | 0x1, 69, 0x7f)
  forge:time(1):midi(MIDI.NoteOff | 0x1, 69, 0x0)
  forge:time(2):midi(MIDI.Bender | 0x1, 0x0, 0x7f)
end
-- process test messages with responder
function apply_sequence(n, seq, forge)
  for frames, atom in seq:foreach() do
    local handled = midiR(frames, forge, atom)
    assert(handled == true)
  end
end
-- check responder output
function check(seq)
  assert(#seq == 2) -- only NoteOn and NoteOff have gone through
endRuns callbacks for received OSC messages.
-- OSCResponder
-- define OSC responder object with callbacks
local oscR = OSCResponder({
  -- listen for '/ping'
  ['/ping'] = function(self, frames, forge, fmt, var1, var2)
    assert(frames == 0)
    assert(fmt == 'if')
    assert(var1 == 12)
    assert(var2 == 12.5)
    -- send a '/pong'
    forge:time(frames):message('/pong', fmt, var1, var2)
  end
}, true) -- route through not matched OSC messages
-- forge test messages
function stash_sequence(forge)
  forge:time(0):message('/ping', 'if', 12, 12.5)
end
-- process test messages with responder
function apply_sequence(n, seq, forge)
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.otype == OSC.Message)
    assert(atom[OSC.messagePath].body == '/ping')
    local handled, matched = oscR(frames, forge, atom)
    assert(handled == true)
    assert(matched == true)
  end
end
-- check responder output
function check(seq)
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.otype == OSC.Message)
    assert(atom[OSC.messagePath].body == '/pong')
  end
endRuns callbacks for received Time messages.
-- TimeResponder
-- define time responder object with callbacks
local timeR = TimeResponder({
  -- listen for speed change
  [Time.speed] = function(self, frames, forge, speed)
    assert( (speed == 0.0) or (speed == 1.0) )
    self.rolling = speed ~= 0.0
  end,
  -- listen for bar change
  [Time.bar] = function(self, frames, forge, bar)
    assert(bar == 0)
  end,
  -- listen for barBeat change
  [Time.barBeat] = function(self, frames, forge, barBeat)
    if forge then -- forge==nil in timeR:apply()
      assert(barBeat == 0.0)
      -- send NoteOn message at each whole beat
      if math.tointeger(barBeat) then
        forge:time(frames):midi(MIDI.NoteOn, 69, 0x7f)
      end
    end
  end,
  -- listen for beatUnit change
  [Time.beatUnit] = function(self, frames, forge, beatUnit)
    assert(beatUnit == 4)
  end,
  -- listen for beatsPerBar change
  [Time.beatsPerBar] = function(self, frames, forge, beatsPerBar)
    assert(beatsPerBar == 4.0)
  end,
  -- listen for beatsPerMinute change
  [Time.beatsPerMinute] = function(self, frames, forge, beatsPerMinute)
    assert(beatsPerMinute == 120.0)
  end,
  -- listen for frame change
  [Time.frame] = function(self, frames, forge, frame)
    assert(frame == 0)
  end,
  -- listen for framesPerSecond change
  [Time.framesPerSecond] = function(self, frames, forge, framesPerSecond)
    assert(framesPerSecond == 48000.0)
  end,
  rolling = false
}, 1.0)
-- index current transport state, can be called at any time
assert(timeR[Time.speed] == 0)
assert(timeR[Time.bar] == 0)
assert(timeR[Time.barBeat] == 0.0)
assert(timeR[Time.beatUnit] == 4)
assert(timeR[Time.beatsPerBar] == 4.0)
assert(timeR[Time.beatsPerMinute] == 120.0)
assert(timeR[Time.frame] == 0)
assert(timeR[Time.framesPerSecond] == 48000.0)
assert(timeR.multiplier == 1.0)
-- push current responder state to temporary stash
function stash(forge)
  assert(timeR:stash(forge) == forge)
end
-- pop and apply current responder state from temporary stash
function apply(atom)
  local handled = timeR:apply(atom)
  assert(handled == true)
end
-- forge test messages
function stash_sequence(forge)
  local obj = forge:time(0):object(Time.Position)
    obj:key(Time.speed):float(1.0) -- start transport
  obj:pop()
end
-- process test messages with responder
function apply_sequence(n, seq, forge)
  local from = 0 -- frame time of 'last event' aka 'period beginning'
  for to, atom in seq:foreach() do
    local handled = timeR(from, to, forge, atom)
    assert(handled == true)
    from = to -- update frame time of 'last event'
  end
  timeR(from, n, forge) -- we must call time responder until 'period ending'
end
-- check responder output
function check(seq)
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.type == MIDI.MidiEvent)
    assert(atom.body == string.char(MIDI.NoteOn, 69, 0x7f))
  end
endRuns callbacks for state handling via patch messages.
-- StateResponder
local urn = Mapper('urn:uuid:e359d24c-e1fe-4b3b-bbda-b58f4b04234d#')
-- define read-only parameter
local period = Parameter{
  [RDFS.label] = 'Period',
  [RDFS.comment] = 'set period',
  [RDFS.range] = Atom.Float,
  [LV2.minimum] = 1.0,
  [LV2.maximum] = 10.0,
  [Units.unit] = Units.s,
  [RDF.value] = 5.0
}
-- define read-write parameter
local frequency = Parameter{
  [RDFS.label] = 'Frequency',
  [RDFS.comment] = 'set frequency',
  [RDFS.range] = Atom.Int,
  [LV2.minimum] = 1,
  [LV2.maximum] = 48000,
  [Units.unit] = Units.hz,
  [RDF.value] = 44100
}
-- define state responder object with callbacks
local stateR = StateResponder({
  [Patch.readable] = {
    [urn.period] = period
  },
  [Patch.writable] = {
    [urn.frequency] = frequency
  }
})
-- push current responder state to temporary stash
function stash(forge)
  assert(stateR:stash(forge) == forge)
end
-- pop and apply current responder state from temporary stash
function apply(atom)
  local handled = stateR:apply(atom)
  assert(handled == true)
end
-- save current responder state to disk
function save(forge)
  assert(stateR:stash(forge) == forge)
end
-- restore current responder state from disk
function restore(atom)
  local handled = stateR:apply(atom)
  assert(handled == true)
end
-- register state upon code reload
function once(n, control, notify, seq, forge)
  assert(stateR:register(0, forge) == forge)
end
-- forge test messages
function stash_sequence(forge)
  forge:time(0):get(urn.period)
end
-- process test messages with responder
function apply_sequence(n, seq, forge)
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.otype == Patch.Get)
    local handled = stateR(frames, forge, atom)
    assert(handled == true)
  end
end
-- check responder output
function check(seq)
  for frames, atom in seq:foreach() do
    assert(frames == 0)
    assert(atom.otype == Patch.Set)
    assert(atom[Patch.property].body == urn.period)
    assert(atom[Patch.value].body == 5.0)
  end
endAdds convenience accessor metamethods to any parameter property table.
-- Parameter
local foo = Parameter({
  [RDFS.label] = 'Foo',
  [RDFS.range] = Atom.Float,
  [LV2.minimum] = 0.0,
  [LV2.maximum] = 1.0,
  [RDF.value] = 0.5
})
local bar = Parameter({
  [RDFS.label] = 'Bar',
  [RDFS.range] = Atom.Int,
  [LV2.minimum] = 0,
  [LV2.maximum] = 10,
  [RDF.value] = 5
})
function stash(forge)
  assert(foo() == 0.5)
  assert(foo[RDF.value] == 0.5)
  foo(0.8)
  assert(foo() == 0.8)
  foo[RDF.value] = 0.9
  assert(foo[RDF.value] == 0.9)
  --TODO
endVarious hopefully useful utility functions.
Conversion from MIDI note to Hertz.
-- midi2cps
assert(midi2cps(69.0) == 440.0) -- relative to 'A-5'==440 Hz
assert(midi2cps(60.0, 60, 12, 400) == 400.0) -- relative to 'C-5'==400 HzConversion from Hertz to MIDI note.
-- cps2midi
assert(cps2midi(440.0) == 69.0) -- relative to 'A-5'==400 Hz
assert(cps2midi(400.0, 60, 12, 400) == 60.0) -- relative to 'C-5'==400 HzConversion from MIDI note number to note name and vice-versa.
-- Note
assert(Note[60] == 'C+4')
assert(Note['A+4'] == 69)AES-128 encryption of byte strings.
-- encrypt
local pass = '2b7e151628aed2a6abf7158809cf4f3c'
local value = 'hello world'
local secret, len = aes128.encode(value, pass)
assert(aes128.decode(secret, pass, len) == value)AES-128 decryption of byte strings.
-- decrypt
local pass = string.char(
  0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
  0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c)
local value = 'hello world'
local secret, len = aes128.encode(value, pass)
assert(aes128.decode(secret, pass, len) == value)Base64 encoding of byte strings.
-- encode
local value = 'hello world'
local encoded = base64.encode(value)Base64 decoding of byte strings.
-- decode
local value = 'hello world'
local encoded = base64.encode(value)
assert(base64.decode(encoded) == value)Ascii85 encoding of byte strings.
-- encode
local value = 'hello world'
local encoded = ascii85.encode(value)Ascii85 decoding of byte strings.
-- decode
local value = 'hello world'
local encoded = ascii85.encode(value)
assert(ascii85.decode(encoded) == value)Frequently used URID values and all MIDI message and controller constants are premapped and conveniently accessible in their own tables.
-- Constants
local ctx = {
  lv2   = Mapper('http://lv2plug.in/ns/lv2core#'),
  param = Mapper('http://lv2plug.in/ns/ext/parameters#'),
  atom  = Mapper('http://lv2plug.in/ns/ext/atom#'),
  time  = Mapper('http://lv2plug.in/ns/ext/time#'),
  midi  = Mapper('http://lv2plug.in/ns/ext/midi#'),
  bufsz = Mapper('http://lv2plug.in/ns/ext/buf-size#'),
  patch = Mapper('http://lv2plug.in/ns/ext/patch#'),
  units = Mapper('http://lv2plug.in/ns/extensions/units#'),
  osc   = Mapper('http://open-music-kontrollers.ch/lv2/osc#'),
  rdf   = Mapper('http://www.w3.org/1999/02/22-rdf-syntax-ns#'),
  rdfs  = Mapper('http://www.w3.org/2000/01/rdf-schema#'),
  moony = Mapper('http://open-music-kontrollers.ch/lv2/moony#'),
  lua   = Mapper('http://lua.org#'),
}
-- Atom URID constants
assert(Atom.Bool == ctx.atom.Bool)
assert(Atom.Int == ctx.atom.Int)
assert(Atom.Long == ctx.atom.Long)
assert(Atom.Float == ctx.atom.Float)
assert(Atom.Double == ctx.atom.Double)
assert(Atom.URID == ctx.atom.URID)
assert(Atom.URI == ctx.atom.URI)
assert(Atom.String == ctx.atom.String)
assert(Atom.Literal == ctx.atom.Literal)
assert(Atom.Path == ctx.atom.Path)
assert(Atom.Chunk == ctx.atom.Chunk)
assert(Atom.Property == ctx.atom.Property)
assert(Atom.Object == ctx.atom.Object)
assert(Atom.Sequence == ctx.atom.Sequence)
assert(Atom.Tuple == ctx.atom.Tuple)
assert(Atom.Vector == ctx.atom.Vector)
assert(Atom.beatTime == ctx.atom.beatTime)
assert(Atom.frameTime == ctx.atom.frameTime)
assert(Atom.childType == ctx.atom.childType)
-- Time URID constants
assert(Time.Position == ctx.time.Position)
assert(Time.barBeat == ctx.time.barBeat)
assert(Time.bar == ctx.time.bar)
assert(Time.beat == ctx.time.beat)
assert(Time.beatUnit == ctx.time.beatUnit)
assert(Time.beatsPerBar == ctx.time.beatsPerBar)
assert(Time.beatsPerMinute == ctx.time.beatsPerMinute)
assert(Time.frame == ctx.time.frame)
assert(Time.framesPerSecond == ctx.time.framesPerSecond)
assert(Time.speed == ctx.time.speed)
-- MIDI URID constants
assert(MIDI.MidiEvent == ctx.midi.MidiEvent)
-- MIDI message and controller constants
for k, v in pairs(MIDI) do
  if k ~= MIDI.MidiEvent then
    print(k, v)
  end
end
-- OSC URID constants
assert(OSC.Event == ctx.osc.Event)
assert(OSC.Packet == ctx.osc.Packet)
assert(OSC.Bundle == ctx.osc.Bundle)
assert(OSC.bundleTimetag == ctx.osc.bundleTimetag)
assert(OSC.bundleItems == ctx.osc.bundleItems)
assert(OSC.Message == ctx.osc.Message)
assert(OSC.messagePath == ctx.osc.messagePath)
assert(OSC.messageArguments == ctx.osc.messageArguments)
assert(OSC.Timetag == ctx.osc.Timetag)
assert(OSC.timetagIntegral == ctx.osc.timetagIntegral)
assert(OSC.timetagFraction == ctx.osc.timetagFraction)
assert(OSC.Nil == ctx.osc.Nil)
assert(OSC.Impulse == ctx.osc.Impulse)
assert(OSC.Char == ctx.osc.Char)
assert(OSC.RGBA == ctx.osc.RGBA)
-- LV2 Param URID constants
assert(Param.sampleRate == ctx.param.sampleRate)
-- LV2 LV2 URID constants
assert(LV2.minimum == ctx.lv2.minimum)
assert(LV2.maximum == ctx.lv2.maximum)
assert(LV2.scalePoint == ctx.lv2.scalePoint)
-- Buffer size URID constants
assert(Buf_Size.minBlockLength == ctx.bufsz.minBlockLength)
assert(Buf_Size.maxBlockLength == ctx.bufsz.maxBlockLength)
assert(Buf_Size.sequenceSize == ctx.bufsz.sequenceSize)
-- Patch URID constants
assert(Patch.Ack == ctx.patch.Ack)
assert(Patch.Delete == ctx.patch.Delete)
assert(Patch.Copy == ctx.patch.Copy)
assert(Patch.Error == ctx.patch.Error)
assert(Patch.Get == ctx.patch.Get)
assert(Patch.Message == ctx.patch.Message)
assert(Patch.Move == ctx.patch.Move)
assert(Patch.Insert == ctx.patch.Insert)
assert(Patch.Patch == ctx.patch.Patch)
assert(Patch.Post == ctx.patch.Post)
assert(Patch.Put == ctx.patch.Put)
assert(Patch.Request == ctx.patch.Request)
assert(Patch.Response == ctx.patch.Response)
assert(Patch.Set == ctx.patch.Set)
assert(Patch.add == ctx.patch.add)
assert(Patch.accept == ctx.patch.accept)
assert(Patch.body == ctx.patch.body)
assert(Patch.context == ctx.patch.context)
assert(Patch.destination == ctx.patch.destination)
assert(Patch.property == ctx.patch.property)
assert(Patch.readable == ctx.patch.readable)
assert(Patch.remove == ctx.patch.remove)
assert(Patch.request == ctx.patch.request)
assert(Patch.subject == ctx.patch.subject)
assert(Patch.sequenceNumber == ctx.patch.sequenceNumber)
assert(Patch.value == ctx.patch.value)
assert(Patch.wildcard == ctx.patch.wildcard)
assert(Patch.writable == ctx.patch.writable)
-- RDF URID constants
assert(RDF.value == ctx.rdf.value)
assert(RDF.type == ctx.rdf.type)
-- RDFS URID constants
assert(RDFS.label == ctx.rdfs.label)
assert(RDFS.range == ctx.rdfs.range)
assert(RDFS.comment == ctx.rdfs.comment)
-- Units URID constants
assert(Units.Conversion == ctx.units.Conversion)
assert(Units.Unit == ctx.units.Unit)
assert(Units.bar == ctx.units.bar)
assert(Units.beat == ctx.units.beat)
assert(Units.bpm == ctx.units.bpm)
assert(Units.cent == ctx.units.cent)
assert(Units.cm == ctx.units.cm)
assert(Units.coef == ctx.units.coef)
assert(Units.conversion == ctx.units.conversion)
assert(Units.db == ctx.units.db)
assert(Units.degree == ctx.units.degree)
assert(Units.frame == ctx.units.frame)
assert(Units.hz == ctx.units.hz)
assert(Units.inch == ctx.units.inch)
assert(Units.khz == ctx.units.khz)
assert(Units.km == ctx.units.km)
assert(Units.m == ctx.units.m)
assert(Units.mhz == ctx.units.mhz)
assert(Units.midiNote == ctx.units.midiNote)
assert(Units.midiController == ctx.units.midiController)
assert(Units.mile == ctx.units.mile)
assert(Units.min == ctx.units.min)
assert(Units.mm == ctx.units.mm)
assert(Units.ms == ctx.units.ms)
assert(Units.name == ctx.units.name)
assert(Units.oct == ctx.units.oct)
assert(Units.pc == ctx.units.pc)
assert(Units.prefixConversion == ctx.units.prefixConversion)
assert(Units.render == ctx.units.render)
assert(Units.s == ctx.units.s)
assert(Units.semitone12TET == ctx.units.semitone12TET)
assert(Units.symbol == ctx.units.symbol)
assert(Units.unit == ctx.units.unit)
-- Moony URID constants
assert(Moony.color == ctx.moony.color)
assert(Moony.syntax == ctx.moony.syntax)
-- Lua URID constants
assert(Lua.lang == ctx.lua.lang)Copyright © 2015-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
This is free software: you can redistribute it and/or modify it under the terms of the Artistic License 2.0 as published by The Perl Foundation.
This source is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Artistic License 2.0 for more details.
You should have received a copy of the Artistic License 2.0 along the source as a COPYING file. If not, obtain it from http://www.perlfoundation.org/artistic_license_2_0.