Moony.lv2

@MOONY_VERSION@

Reference manual

Moony logo

Introduction

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.

Plugin Variants

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.

C1 x C1

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
end

C2 x C2

This 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
end

C4 x C4

This 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
end

A1 x A1

This 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)
end

A2 x A2

This 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)
end

A4 x A4

This 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)
end

C1+A1 x C1+A1

This 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
end

C2+A1 x C2+A1

This 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
end

C4+A1 x C4+A1

This 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
end

Lua libraries

Moony 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:

Log & Debug

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')

Callbacks

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.

run

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.

function run(n, control, notify, seq1, forge1, ..., c1, ...)
n (integer)
number of audio samples of current period
{control, notify} (userdata, userdata)
pair of atom sequence and atom forge object for communication with UI
{seq, forge} [x] (userdata, userdata)
pairs of atom sequence and atom forge objects, with x=[0, 1, 2, 4]
c [x] (number)
control port inputs, with x=[0, 1, 2, 4]
(number)
control port outputs, with x=[0, 1, 2, 4]
-- 'run' callback prototype for moony#a1xa1

function run(n, control, notify, seq, forge)
	-- here we will process events
end

once

The 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.

function once(n, control, notify, seq1, forge1, ..., c1, ...)
n (integer)
number of audio samples of current period
{control, notify} (userdata, userdata)
pair of atom sequence and atom forge object for communication with UI
{seq, forge} [x] (userdata, userdata)
pairs of atom sequence and atom forge objects, with x=[0, 1, 2, 4]
c [x] (number)
control port inputs, with x=[0, 1, 2, 4]
-- '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
end

stash

When 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.

function stash(forge)
forge (userdata)
atom forge object to serialize to
-- '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)
end

apply

When 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.

function apply(atom)
atom (userdata)
atom object to deserialize from
-- '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)
end

save

The 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.

function save(forge)
forge (userdata)
atom forge object to serialize to
-- '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
end

restore

The 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.

function restore(atom)
atom (userdata)
atom object to deserialize from
-- '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
end

Map & Unmap

LV2 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

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:__call(URI) | Map(URI) | Map:__index(URI) | Map[URI]
URI (string)
URI to be mapped
(integer)
mapped URID
-- 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

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:__call(URID) | Unmap(URID) | Unmap:__index(URID) | Unmap[URID]
URID (integer)
URID to be unmapped
(string)
unmapped URI
-- 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)

Mapper

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(prefix)
prefix (string)
prefix string to be appended to
(table)
Mapper table
-- 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)

Blank

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()
(integer)
unique integer ID
-- Blank returns unique integer IDs

local cache = {}

for i = 1, 100 do
	local id = Blank()

	assert(not cache[id])
	cache[id] = true
end

Forge

A forge object is used to serialize arbitrary complex atoms, both simple primitives and nested containers.

Primitive

Atom types that contain single data as part of their body are referred to as primitives.

Bool

Forge a boolean atom, e.g. of type Atom.Bool.

forge:bool(value)
value (boolean)
boolean value to forge
(userdata)
self forge object
-- Forge Bool

function stash_tuple(forge)
	forge:bool(true)
	forge:bool(false)
end

Int

Forge an integer atom, e.g. of type Atom.Int.

forge:int(value)
value (integer)
integer value to forge
(userdata)
self forge object
-- Forge Int

function stash(forge)
	forge:int(12)
end

Long

Forge long integer atom, e.g. of type Atom.Long.

forge:long(value)
value (integer)
integer value to forge
(userdata)
self forge object
-- Forge Long

function stash(forge)
	forge:long(12)
end

Float

Forge single-precision float atom, e.g. of type Atom.Float.

forge:float(value)
value (number)
number value to forge
(userdata)
self forge object
-- Forge Float

function stash(forge)
	forge:float(12.5)
end

Double

Forge double-precision float atom, e.g. of type Atom.Double.

forge:double(value)
value (number)
number value to forge
(userdata)
self forge object
-- Forge Double

function stash(forge)
	forge:double(12.5)
end

URID

Forge URID atom, e.g. of type Atom.URID.

forge:urid(value)
value (integer)
URID value to forge
(userdata)
self forge object
-- Forge URID

local uri = 'urn:uuid:887c5b2e-89f9-4f1d-aa7c-0ac240ea11b5#foo'
local urid = Map[uri]

function stash(forge)
	forge:urid(urid)
end

URI

Forge URI atom, e.g. of type Atom.URI.

forge:uri(value)
value (string)
URI value to forge
(userdata)
self forge object
-- Forge URI

local uri = 'urn:uuid:1f2eb75a-29dc-446b-a8eb-c22d20144a85#foo'

function stash(forge)
	forge:uri(uri)
end

String

Forge string atom, e.g. of type Atom.String.

forge:string(value)
value (string)
string value to forge
(userdata)
self forge object
-- Forge String

function stash(forge)
	forge:string('hello world')
end

Path

Forge path atom, e.g. of type Atom.Path.

forge:path(value)
value (string)
string value to forge
(userdata)
self forge object
-- Forge Path

function stash(forge)
	forge:path('/tmp/xyz')
end

Literal

Forge literal atom, e.g. of type Atom.Literal.

forge:literal(value, datatype=nil, language=nil)
value (string)
string value to forge
datatype (integer)
datatype of literal as integer URID, defaults to 0
language (integer)
language of literal as integer URID, defaults to 0
(userdata)
self forge object
-- 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!'
end

Chunk

Forge chunk atom, e.g. of type Atom.Chunk.

forge:chunk(value)
value (string)
byte string value to forge
(userdata)
self forge object
forge:chunk(value)
value (table)
table with individual values to forge
(userdata)
self forge object
forge:chunk(...)
... (integer)
individual values to forge
(userdata)
self forge object
-- Forge Chunk

function stash_tuple(forge)
	forge:chunk(string.char(0x1, 0x2, 0x3))
	forge:chunk({0x1, 0x2, 0x3})
	forge:chunk(0x1, 0x2, 0x3)
end

MIDI

Forge MIDI atom, e.g. of type MIDI.MidiEvent.

forge:midi(value)
value (string)
byte string value to forge
(userdata)
self forge object
forge:midi(value)
value (table)
table with individual values to forge
(userdata)
self forge object
forge:midi(...)
... (integer)
individual values to forge
(userdata)
self forge object
-- 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)
end

Raw

Forge a custom atom of arbitrary type.

forge:raw(type, value)
type (integer)
type of atom as integer URID
value (string)
byte string value to forge
(userdata)
self forge object
forge:raw(type, value)
type (integer)
type of atom as integer URID
value (table)
table with individual values to forge
(userdata)
self forge object
forge:raw(type, ...)
type (integer)
type of atom as integer URID
... (integer)
individual values to forge
(userdata)
self forge object
-- 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))
end

Typed

Forge an atom of arbitrary type.

forge:typed(type, ...)
type (integer)
type of atom as integer URID
... (boolean | integer | number | string | table)
value(s) to forge, see native forge functions documentation
(userdata)
self forge object
-- 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(...)
end

Atom

Forge an unchanged atom. Useful for cloning whole atoms.

forge:atom(value)
value (userdata)
atom to forge
(userdata)
self forge object
-- 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)
end

Container

Atom types that contain nested atoms as part of their body are referred to as containers.

Sequence

Forge a sequence atom, e.g. an atom of type Atom.Sequence.

forge:sequence(unit=0)
unit (integer)
event time unit as integer URID, defaults to 0, can either be Atom.frameTime or Atom.beatTime
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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
end

Frame Time

Forge frame time of event. Use this on sequences with unit 0 or Atom.frameTime.

forge:frameTime(frames)
frames (integer)
frame time of event
(userdata)
self forge object

Beat Time

Forge beat time of event. Use this on sequences with unit Atom.beatTime.

forge:beatTime(beats)
beats (number)
beat time of event
(userdata)
self forge object

Time

Forge frame or beat time of event. Can be used as syntactic sugar instead of Frame Time or Beat Time.

forge:time(timestamp)
timestamp (integer | number)
frame time (integer) or beat time (number) of event
(userdata)
self forge object

Object

Forge an object atom, e.g. an atom of type Atom.Object.

forge:object(otype=0, id=0)
otype (integer)
object type as integer URID, defaults to 0
id (integer)
object ID as integer URID, defaults to 0
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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
end

Key

Forge key of object property.

forge:key(value, context=0)
value (integer)
key of property as integer URID
context (integer)
context of property as integer URID, defaults to 0
(userdata)
self forge object

Tuple

Forge a tuple atom, e.g. an atom of type Atom.Tuple.

forge:tuple()
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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
end

Vector

Forge a vector atom, e.g. an atom of type Atom.Vector.

forge:vector(type)
type (integer)
child type as integer URID, valid are: Atom.Bool, Atom.Int, Atom.Float, Atom.Long, Atom.Double, Atom.URID
(userdata)
derived container forge object, needs to be finalized with Pop
forge:vector(type, tab)
type (integer)
child type as integer URID, valid are: Atom.Bool, Atom.Int, Atom.Float, Atom.Long, Atom.Double, Atom.URID
tab (table)
table with values of matching vector child type
(userdata)
self forge object
forge:vector(type, ...)
type (integer)
child type as integer URID, valid are: Atom.Bool, Atom.Int, Atom.Float, Atom.Long, Atom.Double, Atom.URID
... (integer | number)
argument list with values of matching vector child type
(userdata)
self forge object
-- 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))
end

Pop

Finalize any derived container forge object.

forge:pop()
(userdata)
parent 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)
end

Autopop

Finalize any derived container forge object automatically via a for-iterator-construction.

forge:autopop()
(function, userdata)
iterator function, parent container forge object
-- 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)
end

OSC

Atom types that contain Open Sound Control bundles or messages as part of their body.

Bundle

Forge a OSC bundle atom, e.g. an atom object of type OSC.Bundle.

forge:bundle(timestamp=1)
timestamp (integer | number)
absolute timestamp in frames (integer) or relative timestamp (number) in seconds, defaults to 1 aka immediate
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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()
end

Message

Forge 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(path, format, ...)
path (string)
OSC path as string
format (string)
OSC format as string
... (integer | number | string)
variable arguments according to format
(userdata)
self forge object
-- 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)
end

Impulse

Forge Impulse argument of OSC message.

forge:impulse()
(userdata)
self forge object

Char

Forge Char argument of OSC message.

forge:char(ch)
ch (integer)
character as integer
(userdata)
self forge object

RGBA

Forge RGBA color argument of OSC message.

forge:rgba(col)
col (integer)
color as 32-bit unsigned integer
(userdata)
self forge object

Timetag

Forge Timetag argument of OSC message.

forge:timetag(timestamp)
timestamp (integer | number)
absolute timestamp in frames (integer) or relative timestamp (number) in seconds, defaults to 1 aka immediate
(userdata)
self forge object
-- 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
end

Patch

Atom types that contain Patch messages as part of their body.

Patch

Forge a patch patch atom, e.g. an atom object of type Patch.Patch.

forge:patch(subject=0, sequenceNumber=0)
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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()
end

Add

Forge add property of patch property.

forge:add()
(userdata)
derived container forge object, needs to be finalized with Pop

Remove

Forge remove property of patch property.

forge:remove()
(userdata)
derived container forge object, needs to be finalized with Pop

Get

Forge a patch get atom, e.g. an atom object of type Patch.Get.

forge:get(property, subject=0, sequenceNumber=0)
property (integer)
patch property as integer URID
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self container object
-- 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
end

Set

Forge a patch set atom, e.g. an atom object of type Patch.Set.

forge:set(property, subject=0, sequenceNumber=0)
property (integer)
patch property as integer URID
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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()
end

Put

Forge a patch put atom, e.g. an atom object of type Patch.Put.

forge:put(subject=0, sequenceNumber=0)
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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()
end

Ack

Forge an ack patch atom, e.g. an atom object of type Patch.Ack.

forge:ack(subject=0, sequenceNumber=0)
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self forge object
-- 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
end

Error

Forge an error patch atom, e.g. an atom object of type Patch.Error.

forge:error(subject=0, sequenceNumber=0)
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self forge object
-- 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
end

Delete

Forge a delete patch atom, e.g. an atom object of type Patch.Delete.

forge:delete(subject, sequenceNumber=0)
subject (integer)
patch subject as integer URID
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self forge object
-- Forge Delete

local urn = Mapper('urn:uuid:cea077fc-b822-4143-b5c5-34c0f7d9f016#')

function stash(forge)
	forge:delete(urn.subj, 1002) -- with sequence number
end

Copy

Forge a copy patch atom, e.g. an atom object of type Patch.Copy.

forge:copy(subject, destination, sequenceNumber=0)
subject (integer)
patch subject as integer URID
destination (integer)
patch destination as integer URID
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self forge object
-- 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
end

Move

Forge a move patch atom, e.g. an atom object of type Patch.Move.

forge:move(subject, destination, sequenceNumber=0)
subject (integer)
patch subject as integer URID
destination (integer)
patch destination as integer URID
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
self forge object
-- 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
end

Insert

Forge a patch insert atom, e.g. an atom object of type Patch.Insert.

forge:insert(subject=0, sequenceNumber=0)
subject (integer)
patch subject as integer URID, defaults to 0
sequenceNumber (integer)
patch sequence number as integer, defaults to 0
(userdata)
derived container forge object, needs to be finalized with Pop
-- 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()
end

Canvas

Atom 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.

BeginPath

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()
(userdata)
self forge object
-- Forge BeginPath

function stash(ctx)
	ctx:beginPath()
end

ClosePath

Forge an atom object of type Canvas.ClosePath. Close a previously begun path.

forge:closePath()
(userdata)
self forge object
-- Forge ClosePath

function stash(ctx)
	ctx:closePath()
end

Arc

Forge an atom object of type Canvas.Arc. Create an Arc structure.

forge:arc(x, y, r, a=0, b=2*Pi)
x (number)
x coordinate of center
y (number)
y coordinate of center
r (number)
radius
a (number)
starting angle in radians, defaults to 0
b (number)
ending angle in radians, defaults to 2*Pi
(userdata)
self forge object
-- 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()
end

CurveTo

Forge an atom object of type Canvas.CurveTo. Append a spline curve segment to path.

forge:curveTo(x1, y1, x2, y2, x3, y3)
x1 (number)
x coordinate of spline point 1
y1 (number)
y1 coordinate of spline point 1
x2 (number)
x coordinate of spline point 2
y2 (number)
y2 coordinate of spline point 2
x3 (number)
x coordinate of spline point 3
y3 (number)
y3 coordinate of spline point 3
(userdata)
self forge object
-- 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()
end

LineTo

Forge an atom object of type Canvas.LineTo. Append line segment to path.

forge:lineTo(x, y)
x (number)
x coordinate of line point
y (number)
y coordinate of line point
(userdata)
self forge object
-- Forge LineTo 

function stash(ctx)
	ctx:beginPath():moveTo(0.0, 0.0):lineTo(1.0, 1.0):closePath():stroke()
end

MoveTo

Forge an atom object of type Canvas.MoveTo. Move to given point and begin new path.

forge:moveTo(x, y)
x (number)
x coordinate of point
y (number)
y coordinate of point
(userdata)
self forge object
-- Forge MoveTo

function stash(ctx)
	ctx:moveTo(0.0, 0.0)
end

Rectangle

Forge an atom object of type Canvas.Rectangle. Create rectangle.

forge:rectangle(x, y, w, h)
x (number)
x coordinate of top left point
y (number)
y coordinate of top left point
w (number)
width of rectangle
h (number)
height of rectangle
(userdata)
self forge object
-- Forge Rectangle

function stash(ctx)
	ctx:rectangle(0.1, 0.1, 0.8, 0.8):fill()
end

PolyLine

Forge an atom object of type Canvas.PolyLine. Create polyline.

forge:polyLine(x1, y1, ...)
x1 (number)
x coordinate of first point
y1 (number)
y coordinate of first poine
(userdata)
self forge object
-- Forge PolyLine

function stash(ctx)
	ctx:beginPath():polyLine(0.1, 0.1, 0.9, 0.1, 0.5, 0.9):closePath():fill()
end

Style

Forge an atom object of type Canvas.Style. Set current stroke and fill color.

forge:style(color)
color (integer)
color in ARGB format
(userdata)
self forge object
-- 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
end

LineWidth

Forge an atom object of type Canvas.LineWidth. Set current line width.

forge:lineWidth(width)
width (number)
line width
(userdata)
self forge object
-- 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()
end

LineDash

Forge an atom object of type Canvas.LineDash. Set current line dash style.

forge:lineDash(dashLen, spaceLen)
dashLen (number)
length of dash lines
spaceLen (number)
length of space between dash lines
(userdata)
self forge object
-- 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()
end

LineCap

Forge an atom object of type Canvas.LineCap. Set current line cap style.

forge:lineCap(cap)
cap (integer)
line cap type URID: Canvas.lineCapButt, Canvas.lineCapRound, Canvas.lineCapSquare
(userdata)
self forge object
-- 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)
end

LineJoin

Forge an atom object of type Canvas.LineJoin. Set current line join style.

forge:lineJoin(join)
join (integer)
line join type URID: Canvas.lineJoinMiter, Canvas.lineJoinRound, Canvas.lineJoinBevel
(userdata)
self forge object
-- 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)
end

MiterLimit

Forge an atom object of type Canvas.MiterLimit. Set current line join miter limit.

forge:miterLimit(limit)
limit (number)
miter limit
(userdata)
self forge object
-- 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)
end

Stroke

Forge an atom object of type Canvas.Stroke. Stroke current path.

forge:stroke()
(userdata)
self forge object
-- Forge Stroke

function stash(ctx)
	ctx:rectangle(0.0, 0.0, 1.0, 1.0):stroke()
end

Fill

Forge an atom object of type Canvas.Fill. Fill current path.

forge:fill()
(userdata)
self forge object
-- Forge Fill

function stash(ctx)
	ctx:rectangle(0.0, 0.0, 1.0, 1.0):fill()
end

Clip

Forge an atom object of type Canvas.Clip. Set current path as clip mask.

forge:clip()
(userdata)
self forge object
-- 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()
end

Save

Forge an atom object of type Canvas.Save. Push/save current state.

forge:save()
(userdata)
self forge object
-- 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()
end

Restore

Forge an atom object of type Canvas.Restore. Pop/restore previously saved state.

forge:restore()
(userdata)
self forge object
-- 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()
end

Translate

Forge an atom object of type Canvas.Translate. Translate current render matrix.

forge:translate(tx, ty)
tx (number)
x coordinate offset of translocation
ty (number)
y coordinate offset of translocation
(userdata)
self forge object
-- 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()
end

Scale

Forge an atom object of type Canvas.Scale. Scale current render matrix.

forge:scale(sx, sy)
sx (number)
x coordinate scaling factor
sy (number)
y coordinate scaling factor
(userdata)
self forge object
-- 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()
end

Rotate

Forge an atom object of type Canvas.Rotate. Rotate current render matrix.

forge:rotate(a)
a (number)
angle of rotation in radians
(userdata)
self forge object
-- 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()
end

Transform

Forge an atom object of type Canvas.Transform. Set current render matrix.

forge:transform(a)
xx (number)
multiplier for x in: x' = xx*x + xy*y + x0
xy (number)
multiplier for y in: x' = xx*x + xy*y + x0
x0 (number)
constant in: x' = xx*x + xy*y + x0
yx (number)
multiplier for y in: y' = yy*y + yx*x + y0
yx (number)
multiplier for x in: y' = yy*y + yx*x + y0
y0 (number)
constant in: y' = yy*y + yx*x + y0
(userdata)
self forge object
-- 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()
end

Reset

Forge an atom object of type Canvas.Reset. Reset current render matrix to identity configuration.

forge:reset()
(userdata)
self forge object
-- 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()
end

FontSize

Forge an atom object of type Canvas.FontSize. Set font size.

forge:fontSize(size)
size (number)
size of font
(userdata)
self forge object
-- Forge FontSize

function stash(ctx)
	ctx:moveTo(0.5, 0.5):fontSize(0.1):fillText('Hello')
end

FillText

Forge an atom object of type Canvas.FillText. Fill/draw text at current position.

forge:fillText(text)
text (string)
text to fill/draw
(userdata)
self forge object
-- Forge FillText

function stash(ctx)
	ctx:moveTo(0.5, 0.5):fontSize(0.1):style(0xff0000ff):fillText('Hello')
end

Atom

Instead 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.__len | #atom
(integer)
size of atom body
atom.type
(integer)
URID of atom type
atom.raw
(string)
raw byte string of atom body
atom.__tostring | tostring(atom)
(string)
string representation of atom

Primitive

Atom types that contain single data as part of their body are referred to as primitives.

Nil

An empty atom, e.g. of type and size 0.

nil.__len | #nil
(integer)
size of atom body
nil.type
(integer)
URID of atom type
nil.body
(nil)
atom body as Lua nil
nil.__tostring | tostring(nil)
(string)
string representation of atom
-- 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
end

Bool

A boolean atom, e.g. of type Atom.Bool and size 4.

bool.__len | #bool
(integer)
size of atom body
bool.type
(integer)
URID of atom type
bool.body
(boolean)
atom body as Lua boolean
bool.__tostring | tostring(bool)
(string)
string representation of atom
-- 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
end

Int

An integer atom, e.g. of type Atom.Int and size 4.

int.__len | #int
(integer)
size of atom body
int.type
(integer)
URID of atom type
int.body
(integer)
atom body as Lua integer
int.__tostring | tostring(int)
(string)
string representation of atom
-- 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
end

Long

A long integer atom, e.g. of type Atom.Long and size 8.

long.__len | #long
(integer)
size of atom body
long.type
(integer)
URID of atom type
long.body
(integer)
atom body as Lua integer
long.__tostring | tostring(long)
(string)
string representation of atom
-- 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
end

Float

A single-precision float atom, e.g. of type Atom.Float and size 4.

float.__len | #float
(integer)
size of atom body
float.type
(integer)
URID of atom type
float.body
(number)
atom body as Lua number
float.__tostring | tostring(float)
(string)
string representation of atom
-- 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
end

Double

A double-precision float atom, e.g. of type Atom.Double and size 8.

double.__len | #double
(integer)
size of atom body
double.type
(integer)
URID of atom type
double.body
(number)
atom body as Lua number
double.__tostring | tostring(double)
(string)
string representation of atom
-- 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
end

URID

An URID atom, e.g. of type Atom.URID and size 4.

urid.__len | #urid
(integer)
size of atom body
urid.type
(integer)
URID of atom type
urid.body
(integer)
atom body as Lua integer
urid.__tostring | tostring(urid)
(string)
string representation of atom
-- 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
end

URI

An URI atom, e.g. of type Atom.URI and variable size. Atom size denotes to string size plus zero byte terminator.

uri.__len | #uri
(integer)
size of atom body
uri.type
(integer)
URID of atom type
uri.body
(string)
atom body as Lua string
uri.__tostring | tostring(uri)
(string)
string representation of atom
-- 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
end

String

A string atom, e.g. of type Atom.String and variable size. Atom size denotes to string size plus zero byte terminator.

string.__len | #string
(integer)
size of atom body
string.type
(integer)
URID of atom type
string.body
(string)
atom body as Lua string
string.__tostring | tostring(string)
(string)
string representation of atom
-- 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
end

Path

A path atom, e.g. of type Atom.Path and variable size. Atom size denotes to string size plus zero byte terminator.

path.__len | #path
(integer)
size of atom body
path.type
(integer)
URID of atom type
path.body
(string)
atom body as Lua string
path.__tostring | tostring(path)
(string)
string representation of atom
-- 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
end

Literal

A 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.

literal.__len | #literal
(integer)
size of atom body
literal.type
(integer)
URID of atom type
literal.body
(string)
atom body as Lua string
literal.datatype
(integer)
URID of data type
literal.body
(integer)
URID of language
literal.__tostring | tostring(literal)
(string)
string representation of atom
literal:unpack()
(string, integer, integer)
atom body as Lua string, URID of data type, URID of language
-- 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)
end

Chunk

A chunk atom, e.g. of type Atom.Chunk and variable size. Atom size denotes to byte size of atom body.

chunk.__len | #chunk
(integer)
size of atom body
chunk.type
(integer)
URID of atom type
chunk.body
(string)
atom body as Lua byte string
chunk.__tostring | tostring(chunk)
(string)
string representation of atom
chunk:unpack(from=1, to=#chunk)
from (nil | integer)
start byte position to unpack from, defaults to 1
to (nil | integer)
end byte position to unpack from, defaults to body size
(integer, integer, ...)
atom body unpacked bytewise as Lua integers
chunk:__index(idx) | chunk[idx]
idx (integer)
byte position to query for
(integer)
byte at position idx of atom body as Lua integer
-- 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)
end

MIDI

A 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.

midi.__len | #midi
(integer)
size of atom body
midi.type
(integer)
URID of atom type
midi.body
(string)
atom body as Lua byte string
midi.__tostring | tostring(midi)
(string)
string representation of atom
midi:unpack(from=1, to=#midi)
from (nil | integer)
start byte position to unpack from, defaults to 1
to (nil | integer)
end byte position to unpack from, defaults to body size
(integer, integer, ...)
atom body unpacked bytewise as Lua integers
midi:__index(idx) | midi[idx]
idx (integer)
byte position to query for
(integer)
byte at position idx of atom body as Lua integer
-- 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)
end

Container

Atom types that contain nested atoms as part of their body are referred to as containers.

Sequence

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.

seq.__len | #seq
(integer)
number of events
seq.type
(integer)
URID of atom type
seq.unit
(integer)
URID of event time unit
seq.__tostring | tostring(seq)
(string)
string representation of atom
seq:foreach()
(integer | number, userdata)
iterates over all atom events returning frame or beat time and event atom.
seq:foreach(...)
... (userdata)
additional sequence(s) to multiplex and iterate over.
(integer | number, userdata, userdata)
multiplexes and iterates over all atom events from all sequences returning frame or beat time, event atom and source sequence atom.
seq:__index(idx) | seq[idx]
idx (integer)
event position to query for
(userdata)
event atom at position idx
-- 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)
end

Tuple

A 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.

tup.__len | #tup
(integer)
number of items
tup.type
(integer)
URID of atom type
tup.__tostring | tostring(tup)
(string)
string representation of atom
tup:unpack(from=1, to=#tup)
from (nil | integer)
start position to unpack from, defaults to 1
to (nil | integer)
end position to unpack from, defaults to body size
(userdata, ...)
atom items unpacked from tuple
tup:foreach()
(integer, userdata)
iterates over all atom items returning index and atom item.
tup:__index(idx) | tup[idx]
idx (integer)
item position to query for
(userdata)
atom item at position idx
-- 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)
end

Object

An 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.

obj.__len | #obj
(integer)
number of properties
obj.type
(integer)
URID of atom type
obj.id
(integer)
URID of atom object ID
obj.otype
(integer)
URID of atom object type
obj.__tostring | tostring(obj)
(string)
string representation of atom
obj:foreach()
(integer, userdata, integer)
iterates over all properties returning property key, atom and context.
obj:__index(key) | obj[key]
key (integer)
URID of key to query for
(userdata)
property atom for key
-- 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)
end

Vector

A 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.

vec.__len | #vec
(integer)
number of items
vec.type
(integer)
URID of atom type
vec.childSize
(integer)
atom vector child size
vec.childType
(integer)
URID of atom vector child type
vec.__tostring | tostring(vec)
(string)
string representation of atom
vec:unpack(from=1, to=#vec)
from (nil | integer)
start position to unpack from, defaults to 1
to (nil | integer)
end position to unpack from, defaults to body size
(userdata, ...)
atom items unpacked from vector
vec:foreach()
(integer, userdata)
iterates over all vector items returning index and atom.
vec:__index(idx) | vec[idx]
idx (integer)
position to query for
(userdata)
atom of item at position idx
-- 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)
end

Stash

Sometimes 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:read()
(userdata)
reference to self as atom object to read from
stash:write()
(userdata)
reference to self as forge object to write to
-- 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)

Options

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:__call(URID) | Options(URID) | Options:__index(URID) | Options[URID]
URID (integer)
key to index options table with as integer URID, e.g. valid keys are: Param.sampleRate, Buf_Size.sequenceSize, Buf_Size.minBlockLength, Buf_Size.maxBlockLength, Ui.updateRate
(userdata)
value of indexed key as atom object
Options:__pairs()
(integer | userdata)
iterates over all options, returning option key and atom userdata.
-- 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)
end

Responder

Responders 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.

MIDIResponder

Runs callbacks for received MIDI messages.

MIDIResponder(responder, through=false)
responder (table)
table with responder callbacks
through (boolean)
flag whether to let unhandled messages through, defaults to false
(userdata)
MIDIResponder object
midiR:__call(frames, forge, atom) | midiR(frames, forge, atom)
frames (integer)
frame time of event
forge (userdata)
forge object
atom (userdata)
atom body of event
(boolean)
flag whether the event was handled, e.g. whether is was any MIDI at all
midiR.through
(boolean)
flag whether to let unhandled messages through
-- 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

-- check dynamically changing through flag
assert(midiR.through == false)
midiR.through = true -- route all MIDI messages not explicitely handled by responder
assert(midiR.through == true)
midiR.through = false -- block again

-- 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
end

OSCResponder

Runs callbacks for received OSC messages.

OSCResponder(responder)
responder (table)
table with responder callbacks
through (boolean)
flag whether to let unhandled messages through, defaults to false
(userdata)
OSCResponder object
oscR:__call(frames, forge, atom) | oscR(frames, forge, atom)
frames (integer)
frame time of event
forge (userdata)
forge object
atom (userdata)
atom body of event
(boolean)
flag whether the event was handled, e.g. whether is was any OSC at all
(boolean)
flag whether any path could be matched
oscR.through
(boolean)
flag whether to let unhandled messages through
-- 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

-- check dynamically changing through flag
assert(oscR.through == true)
oscR.through = false -- block all OSC messages not explicitely handled by responder
assert(oscR.through == false)
oscR.through = true -- route again

-- 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
end

TimeResponder

Runs callbacks for received Time messages.

TimeResponder(responder, multiplier=1.0)
responder (table)
table with responder callbacks
multiplier (number)
virtual multiplier to scale time signal with, defaults to 1.0
(userdata)
TimeResponder object
timeR:__call(from, to, forge, atom) | timeR(from, to, forge, atom)
from (integer)
frame time of last event or period beginning
to (integer)
frame time of current event or period ending
forge (userdata)
forge object
atom (userdata)
atom body of event
(boolean)
flag whether the event was handled, e.g. whether is was any Time event at all
timeR:__index(key) | timeR[key]
key (integer)
time position property key as integer URID, valid keys are: Time:speed, Time:bar, Time.barBeat, Time.beatUnit, Time.beatsPerBar, Time.beatsPerMinute, Time.frame, Time.framesPerSecond
(integer | number)
time position property value
timeR:stash(forge)
forge (userdata)
forge object to stash responder state to
(userdata)
self forge object
timeR:apply(atom)
atom (userdata)
atom object to apply responder state from
(boolean)
flag whether state has been applied successfully
timeR.multiplier
(number)
virtual multiplier to scale time signal with
-- 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
end

StateResponder

Runs callbacks for state handling via patch messages.

StateResponder(responder)
responder (table)
table with responder callbacks
(userdata)
StateResponder object
stateR:__call(frames, forge, atom) | stateR(frames, forge, atom)
frames (integer)
frame time of current event
forge (userdata)
forge object
atom (userdata)
atom body of event
(boolean)
flag whether the event was handled, e.g. whether is was any patch event at all
stateR:register(frames, forge)
frames (integer)
frame time of current event, usually 0 aka start of period
forge (userdata)
forge object to register state to
(userdata)
self forge object
stateR:sync(frames, forge)
frames (integer)
frame time of current event
forge (userdata)
forge object to sync state to
(userdata)
self forge object
stateR:stash(forge)
forge (userdata)
forge object to stash responder state to
(userdata)
self forge object
stateR:apply(atom)
atom (userdata)
atom object to apply responder state from
(boolean)
flag whether state has been applied successfully
-- 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
end

Parameter

Adds convenience accessor metamethods to any parameter property table.

Parameter(param)
param (table)
table with parameter properties
(table)
table with parameter properties plus metamethods
param:__call(value, frames=nil, forge=nil) | param(value, frames=nil, forge=nil)
value (integer | number | string | boolean)
new value to set parameter to
frames (integer)
frame time value is set on, defaults to nil
forge (userdata)
forge to send patch messages to, defaults to nil
param:__call() | param()
(integer | number | string | boolean)
current value parameter is set to
-- 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
end

Utilities

Various hopefully useful utility functions.

midi2cps

Conversion from MIDI note to Hertz.

midi2cps(note, base=69.0, noct=12.0, fref=440.0)
note (number)
MIDI note, fractions are allowed
base (number)
MIDI base note corresponding to reference frequency, defaults to 69.0 (A-5)
noct (number)
number of notes per octave, default to 12.0
fref (number)
reference frequency corresponding to MIDI base note, defaults to 440.0 Hz (A-5)
(number)
corresponding frequency in Hz
-- 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 Hz

cps2midi

Conversion from Hertz to MIDI note.

cps2midi(cps, base=69.0, noct=12.0, fref=440.0)
cps (number)
frequency in Hz
base (number)
MIDI base note corresponding to reference frequency, defaults to 69.0 (A-5)
noct (number)
number of notes per octave, default to 12.0
fref (number)
reference frequency corresponding to MIDI base note, defaults to 440.0 Hz (A-5)
(number)
corresponding 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 Hz

Note

Conversion from MIDI note number to note name and vice-versa.

Note.__index(note) | Note[note]
note (integer)
MIDI Note number
(string)
MIDI note name
Note.__index(name) | Note[name]
name (string)
MIDI Note name
(integer)
MIDI note number
-- Note

assert(Note[60] == 'C+4')
assert(Note['A+4'] == 69)

AES-128

Encode

AES-128 encryption of byte strings.

aes128.encode(value, pass)
value (string)
clear text byte string to encode
pass (string)
128-bit passphrase, either as raw byte string or as hex-encoded string
(string, integer)
corresponding secret byte string, length of encoded clear text
-- encrypt

local pass = '2b7e151628aed2a6abf7158809cf4f3c'
local value = 'hello world'

local secret, len = aes128.encode(value, pass)
assert(aes128.decode(secret, pass, len) == value)

Decode

AES-128 decryption of byte strings.

aes128.decode(value, pass)
value (string)
secret byte string to decode
pass (string)
128-bit passphrase, either as raw byte string or as hex-encoded string
len (integer)
length of encoded clear text
(string)
corresponding clear text byte string
-- 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

Encode

Base64 encoding of byte strings.

base64.encode(value)
value (string)
clear text byte string to encode
(string)
corresponding encoded string
-- encode

local value = 'hello world'

local encoded = base64.encode(value)

Decode

Base64 decoding of byte strings.

base64.decode(value)
value (string)
encoded string to decode
(string)
corresponding clear text byte string
-- decode

local value = 'hello world'

local encoded = base64.encode(value)
assert(base64.decode(encoded) == value)

Ascii85

Encode

Ascii85 encoding of byte strings.

ascii85.encode(value)
value (string)
clear text byte string to encode
(string)
corresponding encoded string
-- encode

local value = 'hello world'

local encoded = ascii85.encode(value)

Decode

Ascii85 decoding of byte strings.

ascii85.decode(value)
value (string)
encoded string to decode
(string)
corresponding clear text byte string
-- decode

local value = 'hello world'

local encoded = ascii85.encode(value)
assert(ascii85.decode(encoded) == value)

Constants

Frequently used URID values and all MIDI message and controller constants are premapped and conveniently accessible in their own tables.

(table):__index(key) | (table)[key]
key (string)
key to index constants table with
(integer)
corresponding URID or integer for given key
-- 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)

License

Copyright © 2015-2021 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.