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

- base
- coroutine
- table
- string
- math
- utf8
- debug

The following standard Lua libraries are disabled:

- io
- package
- os

The following base functions are disabled:

- loadfile
- dofile

The following math functions are disabled (use the dedicated random utility library instead):

- math.random
- math.randomseed

Additionally to the standard libraries, Moony ships the following commonly used utility libraries:

- lpeg
- base64
- ascii85
- mathx
- complex
- random

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