[SCM] libmapper/master: Adding java binding

tiago at users.alioth.debian.org tiago at users.alioth.debian.org
Tue Oct 28 05:30:51 UTC 2014


The following commit has been merged in the master branch:
commit 533100131998adfa411448305960a5c0c2036a8a
Author: Tiago Bortoletto Vaz <tiago at debian.org>
Date:   Mon Oct 27 21:54:26 2014 -0400

    Adding java binding

diff --git a/README.markdown b/README.markdown
index d9f6745..5f981e6 100644
--- a/README.markdown
+++ b/README.markdown
@@ -7,14 +7,14 @@ a network and allowing arbitrary "mappings" to be dynamically created
 between them.
 
 A "mapping", or "connection" associated with relational properties,
-consists of an Open Sound Control stream being established between a
-source and a destination signal--the source is translated to the
-destination's expected format, with some mathematical expression used
-to condition the transmitted values as desired.  This can be used for
-example to connect a set of sensors to a synthesizer's input
-parameters.
-
-To get started quickly with libmapper, be sure to read the tutorial,
+consists of an [Open Sound Control](http://opensoundcontrol.org/)
+stream being established between a source and a destination signal – the
+source is translated to the destination's expected format, with some
+mathematical expression used to condition the transmitted values as
+desired.  This can be used for example to connect a set of sensors to
+a synthesizer's input parameters.
+
+To get started quickly with libmapper, be sure to read the tutorials,
 found in the "doc" folder in this distribution.
 
 History of the mapper project
@@ -32,10 +32,10 @@ protocol in C; hence, libmapper.
 
 We were already using [Open Sound Control](http://opensoundcontrol.org/)
 for this purpose, but needed a way to dynamically change the
-mappings--not only to modify the destinations of OSC messages, but
+mappings – not only to modify the destinations of OSC messages, but
 also to change scaling, or to introduce derivatives, integration,
 frequency filtering, smoothing, etc.  Eventually this grew into the
-use of a multicast bus to publish signal metadata and administrate
+use of a multicast bus to publish signal metadata and to administrate
 peer-to-peer connections between machines on a local subnet, with the
 ability to specify arbitrary mathematical expressions that can be used
 to perform signal conditioning.
@@ -46,7 +46,7 @@ responsible for arbitrating connections or republishing data, and we
 also wanted to avoid needing to keep a central database of serial
 numbers or other unique identifiers for devices produced in the lab.
 Instead, common communication is performed on a multicast UDP port,
-that all nodes listen on, and this is used to implement a
+which all nodes listen on, and this is used to implement a
 collision-handling protocol that can assign a unique ID to each device
 that appears.  Actual signal data is sent directly from a sender
 device to the receiver's IP and UDP port.
@@ -75,25 +75,25 @@ products such as Camille Troillard's [Osculator](http://www.osculator.net/)
 and STEIM's [Junxion](http://www.steim.org/steim/junxion_v4.html),
 albeit certainly more "barebones" for the moment.
 
-In addition to passing signal data through mapping connections, libmapper
-provides the ability to query the state of the inputs on a destination device.
+In addition to passing signal data through mapping connections,
+libmapper provides the ability to query or stream the state of the inputs
+on a destination device.
 This permits the use of intermediate devices that use supervised machine
 learning techniques to "learn" mappings automatically.
 
 A major difference in libmapper's approach to handling devices is the
 idea that each "driver" can be a separate process, on the same or
 different machines in the network.  By creating a C library with a
-basic interface and by providing SWIG bindings, we hope to enable a
-very wide variety of programming language bindings, to allow any
-choice of development strategy.^[At this time, the SWIG bindings only
-work for Python.  Support for more languages, particularly Java, is in
-the works.]  Another advantage of a C library is portability: we have
-demonstrated libmapper working on a Gumstix device, an
-ethernet-enabled ARM-based microcomputer running Linux that can be
-easily embedded directly into an instrument.
-
-We also provide an "external" Max/MSP and [PureData](http://puredata.info)
-object, to help integrate libmapper support into programs created in
+basic interface and by providing SWIG and Java bindings, we hope to
+enable a very wide variety of programming language bindings.^[At this time,
+the SWIG bindings only work for Python.]
+Another advantage of a C library is portability: we have demonstrated
+libmapper working on a Gumstix device, an ethernet-enabled ARM-based
+microcomputer running Linux that can be easily embedded directly into
+an instrument.
+
+We also provide "external" Max/MSP and [PureData](http://puredata.info)
+objects, to help integrate libmapper support into programs created in
 these popular dataflow languages.
 
 Known limitations
@@ -116,20 +116,19 @@ data-oriented processes don't fit directly into the "connection"
 paradigm used here.  Of course, many of these techniques can easily be
 implemented as "intermediate" devices that sit between a sender and
 receiver.  Another category of mappings that can currently only be
-solved this way include many-to-one mappings--libmapper currently has
+solved this way include many-to-one mappings – libmapper currently has
 no special handling of multiple devices sending to one receiver, and
 the values are simply interleaved, whereas what is intended is
 probably some combining function such as addition, multiplication, or
-thresholding.  It will be possible to solve this latter issue by using
-some form of receiver-side routers which can handle expressions
-containing multiple input variables (See future plans below).
+thresholding.  We are working towards solving this latter issue by using
+receiver-side routers which can handle expressions containing multiple
+input variables (See future plans below).
 Many-to-one mapping functionality was not an immediate priority, since
 we feel that in practise it is actually quite rare to have a case
 where such a combining function should be arbitrarily modifiable by a
 user.  In the real world, dependencies between signals often have a
 semantic significance which would be better handled internally to the
-sender or the receiver rather than in the mapper layer.  Nonetheless,
-we hope to tackle this problem eventually.
+sender or the receiver rather than in the mapper layer.
 
 One impact of peer-to-peer messaging is that it may suffer from
 redundancy.  In general it may be more efficient to send all data once
@@ -144,7 +143,7 @@ non-trivial work--for example, in libmapper the concept of a "router"
 is actually an independent node that a device sends messages to, which
 then translates and rebroadcasts; it just happens that the router is
 embedded in the sender because that was the most efficient place to
-have it in our scenario.  Optimizing of message-passing efficiency/network	
+have it in our scenario.  Optimizing of message-passing efficiency/network
 topology is not a near-term goal, but in the meantime it is entirely
 possible to use libmapper to explicitly create a centralized network
 if desired; this will simply imply more overhead in managing connections.
@@ -164,18 +163,6 @@ similar.  In addition, on embedded devices, it might make sense to
 memory, while still allowing the use of the libmapper admin protocol
 and GUIs to dynamically experiment with mapping.
 
-Although we provide a basic cross-platform GUI using
-[Qt](http://qt.nokia.com/), the most advanced user interface is still
-the Max/MSP implementation.  Although this works extremely well, it is
-limited to platforms supported by Max/MSP.  (In other words, not
-Linux.)  There are currently plans to create a web-based front-end
-that will be cross-platform.  Better ways of visualizing and
-interacting with the network are also an active research topic.
-
-Currently the only supported data types are 32-bit integers and 32-bit
-floating point.  This covers most of our use cases, but we hope to
-expand this catalogue eventually.
-
 In the syntax for mathematical expressions, we include a method for
 indexing previous values to allow basic filter construction.  This is
 done by index, but we also implemented a syntax for accessing previous
@@ -183,11 +170,13 @@ state according to time in seconds.  This feature is not yet useable,
 but in the future interpolation will be performed to allow referencing
 of time-accurate values.
 
-Currently any "filter" implemented by an expression may suffer
-somewhat from jitter due to irregular timing in the sender, network
-delay, etc.  We plan to tackle this problem by allowing timestamped
-signals using OSC bundles, which will likely require some changes to
-liblo, the OSC implementation used by libmapper.
+The explicit use of timing information may also be useful for certain
+mapping scenarios, such as "debouncing" signal updates or adding adaptive
+delays.  We are gradually working towards such functionality, developing
+a syntax for referring to timing data when configuring connection properties
+and implementing a lighweight synchronization scheme between linked devices
+that will permit jitter mitigation and correct handling of delays when
+devices are running on separate computers.
 
 As mentioned above, the ability to design many-to-one mapping connections 
 will also be explored in future development of libmapper.
@@ -207,9 +196,9 @@ or from the libmapper page on the IDMIL website,
 Building and using libmapper
 ----------------------------
 
-Please see the separate documentation for building libmapper, a
-tutorial on using its API, and doxygen-generated API documentation, in
-the "doc" directory.
+Please see the separate documentation for building libmapper, tutorials
+on using its API in C, C++, Python, Java, MaxMSP and Pure Data, and
+doxygen-generated API documentation, in the "doc" directory.
 
 License
 -------
diff --git a/configure.ac b/configure.ac
index 74c52ba..8a4d53a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -78,6 +78,44 @@ AM_PROG_LIBTOOL
 LT_LIB_M
 LIBS="$LIBS $LIBM"
 
+# Check for C++11 features
+_CXXFLAGS="$CXXFLAGS"
+AC_LANG_PUSH([C++])
+
+AC_MSG_CHECKING([whether C++11 lambdas are supported])
+CXXFLAGS="$_CXXFLAGS -std=c++11"
+AC_COMPILE_IFELSE(
+  [AC_LANG_PROGRAM([#include <functional>],[[[]](std::function<void()> f){f();}([[]](){0;});])],
+    [AC_MSG_RESULT([yes])
+     HAVE_LAMBDA=yes],
+    [
+CXXFLAGS="$_CXXFLAGS -std=c++0x"
+AC_COMPILE_IFELSE(
+  [AC_LANG_PROGRAM([#include <functional>],[[[]](std::function<void()> f){f();}([[]](){0;});])],
+    [AC_MSG_RESULT([yes])
+     HAVE_LAMBDA=yes],
+    [
+CXXFLAGS="$_CXXFLAGS -std=c++11 -stdlib=libc++"
+AC_COMPILE_IFELSE(
+  [AC_LANG_PROGRAM([#include <functional>],[[[]](std::function<void()> f){f();}([[]](){0;});])],
+    [AC_MSG_RESULT([yes])
+     HAVE_LAMBDA=yes],
+    [AC_MSG_RESULT([no])
+     CXXFLAGS="$_CXXFLAGS"])])])
+AM_CONDITIONAL([HAVE_LAMBDA],[test x$HAVE_LAMBDA = xyes])
+
+# If we can add -Qunused-arguments, add it.
+# This error occurs when ccache and clang are used together.
+AC_MSG_CHECKING([whether to add -Qunused-arguments (C++)])
+_CXXFLAGS="$CXXFLAGS"
+CXXFLAGS="$_CXXFLAGS -Qunused-arguments"
+AC_COMPILE_IFELSE(
+  [AC_LANG_PROGRAM([],[])],
+  [AC_MSG_RESULT([yes])],
+  [AC_MSG_RESULT([no])
+   CXXFLAGS="$_CXXFLAGS"])
+AC_LANG_POP([C++])
+
 # Check options
 AC_ARG_ENABLE(debug,
    [AS_HELP_STRING([--enable-debug],[compile with debug flags])],
diff --git a/debian/changelog b/debian/changelog
index b095987..25dc1be 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,5 +1,5 @@
-libmapper (0.3~git+20140610-1) unstable; urgency=low
+libmapper (0.3~git+20141027-1) unstable; urgency=low
 
   * Initial Release. (Closes: #737238)
 
- -- Tiago Bortoletto Vaz <tiago at debian/org>  Tue, 17 Jun 2014 19:29:22 -0400
+ -- Tiago Bortoletto Vaz <tiago at debian.org>  Mon, 27 Oct 2014 21:49:05 -0400
diff --git a/debian/control b/debian/control
index 7fb97d2..bad8b59 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: libs
 Priority: optional
 Maintainer: Debian Multimedia Maintainers <pkg-multimedia-maintainers at lists.alioth.debian.org>
 Uploaders: Tiago Bortoletto Vaz <tiago at debian.org>
-Build-Depends: debhelper (>= 8.1.3~)
+Build-Depends: debhelper (>= 8.1.3~), dh-autoreconf, liblo-dev, zlib1g-dev, pkg-config, python-dev
 Standards-Version: 3.9.2
 Homepage: http://libmapper.org
 Vcs-Browser: http://git.debian.org/?p=pkg-multimedia/libmapper.git
@@ -40,4 +40,20 @@ Description: Library for connecting data signals on a shared network
  creating and using systems for interactive control of media synthesis.
  .
  This package provides the files necessary to compile programs which
- use pHash library.
+ use libmapper library.
+
+Package: python-libmapper
+Architecture: any
+Section: python
+Depends: libmapper0 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}
+Description: Library for connecting data signals on a shared network
+ libmapper is a cross-platform software library for declaring data
+ signals on a shared network and enabling arbitrary connections to be
+ made between them. It creates a distributed mapping system/network, with
+ no central points of failure, the potential for tight collaboration and
+ easy parallelization of media synthesis.
+ .
+ The main focus of libmapper development is to provide tools for
+ creating and using systems for interactive control of media synthesis.
+ .
+ This package contains SWIG generated Python bindings.
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 1d7bb34..83ebffa 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -7,8 +7,10 @@ DOX=libmapper.doxyfile
 
 EXTRA_DIST=expression_syntax.md how_to_compile_and_run.md windows.md	\
            tutorials/tutorial_c.md tutorials/tutorial_introduction.md	\
-           tutorials/tutorial_maxmsp_central.md tutorials/tutorial_maxmsp_multiobj.md \
-           tutorials/tutorial_pure_data.md tutorials/tutorial_python.md
+           tutorials/tutorial_maxmsp_central.md                         \
+           tutorials/tutorial_maxmsp_multiobj.md                        \
+           tutorials/tutorial_pure_data.md tutorials/tutorial_python.md \
+           tutorials/tutorial_java.md
 
 INSTIMAGES=html/doxygen.png
 
diff --git a/doc/expression_syntax.md b/doc/expression_syntax.md
index e4b9178..da60256 100644
--- a/doc/expression_syntax.md
+++ b/doc/expression_syntax.md
@@ -12,6 +12,7 @@ Expressions in libmapper must always be presented in the form
 `y = x`, where `x` refers to the updated source value and `y` is
 the computed value to be forwarded to the destination.
 
+
 Available Functions
 -------------------
 
@@ -64,26 +65,81 @@ Available Functions
 * `y = midiToHz(x)` — convert MIDI note value to Hz
 * `y = hzToMidi(x)` — convert Hz value to MIDI note
 
+Vectors
+=======
+
+Individual elements of variable values can be accessed using the notation
+`<variable>[<index>]`. The index specifies the vector element, and
+obviously must be `>=0`. Expressions must match the vector lengths of the
+source and destination signals, and can be used to translate between
+signals with different vector lengths.
+
+### Vector examples
+
+* `y = x[0]` — simple vector indexing
+* `y = x[1:2]` — specify a range within the vector
+* `y = [x[1], x[2], x[0]]` — rearranging ("swizzling") vector elements
+* `y[1] = x` — apply update to a specific element of the output
+* `y[0:2] = x` — apply update to elements `0-2` of the output vector
+* `[y[0], y[2]] = x` — apply update to output vector elements `y[0]` and `y[2]` but
+leave `y[1]` unchanged.
+
+### Vector functions
+
+There are two special functions that operate across all elements of the vector:
+
+* `y = any(x)` — output `1` if **any** of the elements of `x` are non-zero, otherwise output `0`
+* `y = all(x)` — output `1` if **all** of the elements of `x` are non-zero, otherwise output `0`
 
 FIR and IIR Filters
 ===================
 
 Past samples of expression input and output can be accessed using the notation
-`<variable>{<index>}`. The index specifies the amount of delay in samples, and
-obviously must be `<=0` for the input and `<0` for the output ( i.e. it cannot
-be a value that has not been provided or computed yet ).
+`<variable>{<index>}`. The index specifies the history index in samples, and must be `<=0` for the input (with `0` representing the present input sample) and `<0` for the expression output ( i.e. it cannot be a value that has not been provided or computed yet ).
 
-Using only delayed samples of the expression *input* `x` we can create **Finite
+Using only past samples of the expression *input* `x` we can create **Finite
 Impulse Response** ( FIR ) filters - here are some simple examples:
 
 * `y = x - x{-1}` — 2-sample derivative
 * `y = x + x{-1}` — 2-sample integral
 
-Using delayed samples of the expression *output* `y` we can create **Infinite
+Using past samples of the expression *output* `y` we can create **Infinite
 Impulse Response** ( IIR ) filters - here are some simple examples:
 
 * `y = y{-1} * 0.9 + x * 0.1` — exponential moving average with current-sample-weight of `0.1`
 * `y = y{-1} + x - 1` — leaky integrator with a constant leak of `1`
 
 Of course the filter can contain references to past samples of both `x` and `y` -
-currently libmapper will reject expressions referring to sample delays `> 100`.
\ No newline at end of file
+currently libmapper will reject expressions referring to sample delays `> 100`.
+
+Initializing filters
+--------------------
+Past values of the filter output `y{-n}` can be set using additional sub-expressions, separated using commas:
+
+* `y = y{-1} + x`, `y{-1} = 100`
+
+Filter initialization takes place the first time the expression evaluator is called;
+after this point the initialization sub-expressions will not be evaluated. This means
+the filter could be initialized with the first sample of `x` for example:
+
+* `y = y{-1} + x`, `y{-1} = x * 2`
+
+A function could also be used for initialization:
+
+* `y = y{-1} + x`, `y{-1} = uniform(1000)` — initialize `y{-1}` to a random value
+
+Any past values that are not explicitly initialized are given the value `0`.
+
+
+User-Declared Variables
+=======================
+
+Up to 8 additional variables can be declared as-needed in the expression. The variable names can be any string except for the reserved variable names `x` and `y` or the name of an existing function.  The values of these variables are stored with the connection context and can be accessed in subsequent calls to the evaluator. In the following example, the user-defined variable `ema` is used to keep track of the `exponential moving average` of the input signal value `x`, *independent* of the output value `y` which is set to give the difference between the current sample and the moving average:
+
+* `ema = ema{-1} * 0.9 + x * 0.1`, `y = x - ema`
+
+Just like the output variable `y` we can initialize past values of user-defined variables before expression evaluation. **Initialization will always be performed first**, after which sub-expressions are evaluated **in the order they are written**. For example, the expression string `y=ema*2, ema=ema{-1}*0.9+x*0.1, ema{-1}=90` will be evaluated in the following order:
+
+1. `ema{-1}=90` — initialize the past value of variable `ema` to `90`
+2. `y=ema*2` — set output variable `y` to equal the **current** value of `ema` multiplied by `2`. The current value of `ema` is `0` since it has not yet been set.
+3. `ema=ema{-1}*0.9+x*0.1` — set the current value of `ema` using current value of `x` and the past value of `ema`
diff --git a/doc/tutorials/tutorial_c.md b/doc/tutorials/tutorial_cpp.md
similarity index 57%
copy from doc/tutorials/tutorial_c.md
copy to doc/tutorials/tutorial_cpp.md
index 01820e6..d08db8b 100644
--- a/doc/tutorials/tutorial_c.md
+++ b/doc/tutorials/tutorial_cpp.md
@@ -1,43 +1,10 @@
-Getting started
-===============
-
-Since _libmapper_ uses GNU autoconf, getting started with the library
-is the same as any other library on Linux; use `./configure` and then
-`make` to compile it.  You'll need `swig` available if you want to
-compile the Python bindings.  On Mac OS X, we provide a precompiled
-Framework bundle for 32- and 64-bit Intel platforms, so using it with
-XCode should be a matter of including it in your project.
-
-// TODO: macports/fink/homebrew/etc
-
-Overview of the API organization
-================================
-
-If you take a look at the API documentation, there is a section called
-"modules".  This is divided into the following sections:
-
-* Signals
-* Devices
-* Admins
-* Device database
-* Signal database
-* Connections database
-* Links database
-* Monitors
-
-For this tutorial, the only sections to pay attention to are Devices
-and Signals.  Admins is reserved for providing custom networking
-configurations, but in general you don't need to worry about it.
-
-Monitor and the various database modules are used to keep track of
-what devices, signals and connections are on the network.  Devices do
-not need to worry about this.  It is used mainly for creating user
-interfaces for mapping design and will also not be covered here.
-
-Functions and types from each module are prefixed with
-`mapper_<module>_`, in order to avoid namespace clashing.  However,
-since this is a bit verbose, it is shortened to `mdev_` and `msig_` for
-device and signal functions respectively.
+Using libmapper and C++
+============
+
+C++ bindings are supplied
+
+    #include <mapper/mapper_cpp.h>
+
 
 Devices
 =======
@@ -46,48 +13,30 @@ Creating a device
 -----------------
 
 To create a _libmapper_ device, it is necessary to provide a few
-parameters to `mdev_new`:
-
-    mapper_device mdev_new( const char *name_prefix,
-                            int initial_port,
-                            mapper_admin admin );
-
-Every device on the network needs a name and port.  In fact the
-requested name and port are only "starting values".  There is an
-initialization period after a device is created where a unique ordinal
-is chosen to append to the device name.  This allows multiple devices
-with the same name to exist on the network.
-
-Similarly, each device gets a unique port to use for exchanging signal
-data.  The provided port number is the "starting port", but the
-allocation algorithm will possibly choose another port number close to
-it if someone else on the network is already using it.  We usually use
-a port number of 9000, and let the system decide if this is
-appropriate.^[Strictly this is only necessary for devices on the same
-computer, but port numbers are in abundance so we just allocate one
-per device to keep things consistent.]
-
-The third parameter of mdev_new is an optional admin instance.  It is
-not necessary to provide this, but can be used to specify different
-networking parameters, such as specifying the name of the network
-interface to use.
+parameters the constructor:
+
+    mapper::Device dev( const char *name, mapper.Admin admin );
+    mapper::Device dev( std::string name, mapper.Admin admin );
+
+In regular usage only the first argument is needed. The optional
+"admin" parameter can be used to specify different networking
+parameters, such as specifying the name of the network interface to use.
 
 An example of creating a device:
 
-    mapper_device my_device = mdev_new( "test", 9000, 0 );
+    mapper::Device dev( "test" );
 
 Polling the device
 ------------------
 
 The device lifecycle looks like this, in terrible ASCII diagram art:
 
-    mdev_new -> mdev_poll +-> mdev_free
-                  |       |
-                  +----<--+
+    creation --> poll --+--> destruction
+                  |     |
+                  +--<--+
 
 In other words, after a device is created, it must be continuously
-polled during its lifetime, and then explicitly freed when it is no
-longer needed.
+polled during its lifetime.
 
 The polling is necessary for several reasons: to respond to requests
 on the admin bus; to check for incoming signals; to update outgoing
@@ -98,23 +47,21 @@ not extremely sensitive, but should be at least 100 ms or less.  The
 faster it is polled, the faster it can handle incoming and outgoing
 signals.
 
-The `mdev_poll` function can be blocking or non-blocking, depending on
-how you want your application to behave.  It takes a number of
-milliseconds during which it should do some work, or 0 if it should
-check for any immediate actions and then return without waiting:
+The `poll()` function can be blocking or non-blocking, depending on
+how you want your application to behave.  It takes an optional number
+of milliseconds during which it should do some work before returning:
 
-    int mdev_poll( mapper_device md,
-                   int block_ms );
+    int dev.poll( int block_ms );
 
 An example of calling it with non-blocking behaviour:
 
-    mdev_poll( my_device, 0 );
+    dev.poll();
 
 If your polling is in the middle of a processing function or in
 response to a GUI event for example, non-blocking behaviour is
 desired.  On the other hand if you put it in the middle of a loop
 which reads incoming data at intervals or steps through a simulation
-for example, you can use `mdev_poll` as your "sleep" function, so that
+for example, you can use `poll()` as your "sleep" function, so that
 it will react to network activity while waiting.
 
 It returns the number of messages handled, so optionally you could
@@ -131,21 +78,10 @@ stated differently, it will "time-quantize" the message handling.
 This is not necessarily bad, but you should be aware of this effect.
 
 Since there is a delay before the device is completely initialized, it
-is sometimes useful to be able to determine this using `mdev_ready`.
-Only when `mdev_ready` returns non-zero is it valid to use the
+is sometimes useful to be able to determine this using `ready()`.
+Only when `dev.ready()` returns non-zero is it valid to use the
 device's name.
 
-Freeing the device
-------------------
-
-It is necessary to explicitly free the device at the end of your
-program.  This not only frees memory, but also sends some messages to
-"politely" remove itself from the network.
-
-An example of freeing a device:
-
-    mdev_free( my_device );
-
 Signals
 =======
 
@@ -162,31 +98,32 @@ Creating a signal
 A signal requires a bit more information than a device, much of which
 is optional:
 
-    mapper_signal mdev_add_input( mapper_device dev,
-                                  const char *name,
-                                  int length,
-                                  char type,
-                                  const char *unit,
-                                  void *minimum,
-                                  void *maximum,
-                                  mapper_signal_handler *handler,
-                                  void *user_data );
-
-    mapper_signal mdev_add_output( mapper_device dev,
-                                   const char *name,
-                                   int length,
-                                   char type,
-                                   const char *unit,
-                                   void *minimum,
-                                   void *maximum );
+* a name for the signal (must be unique within a devices inputs or outputs)
+* the signal's vector length
+* the signal's data type expressed as a character 'i', 'f', 'd'
+* the signal's unit (optional)
+* the signal's minimum value (optional)
+* the signal's maximum value (optional)
+
+for input signals there is an additional argument:
+
+* a function to be called when the signal is updated
+
+examples:
+
+    mapper::Signal sig_in = dev.add_input( "/my_input", 1, 'f', "m/s", 0, 0, h )
+
+    int min[4] = {1,2,3,4};
+    int max[4] = {10,11,12,13};
+    mapper::Signal sig_out = dev.add_output( "/my_output", 4, 'i', 0, min, max )
 
 The only _required_ parameters here are the signal "length", its name,
 and data type.  Signals are assumed to be vectors of values, so for
 usual single-valued signals, a length of 1 should be specified.  A
 signal name should start with "/", as this is how it is represented in
 the OSC address.  (One will be added if you forget to do this.)
-Finally, supported types are currently 'i' or 'f' (specified as
-characters in C, not strings), for `int` or `float` values,
+Finally, supported types are currently 'i', 'f', or 'd' (specified as
+characters in C, not strings), for `int`, `float`, or `double` values,
 respectively.
 
 The other parameters are not strictly required, but the more
@@ -201,9 +138,10 @@ this future development by simply providing unit information whenever
 it is available.  It is also helpful documentation for users.
 
 Notice that optional values are provided as `void*` pointers.  This is
-because a signal can either be `float` or `int`, and your maximum and
-minimum values should correspond in type.  So you should pass in a
-`float*` or `int*` by taking the address of a local variable.
+because a signal can either be `int`, `float` or `double`, and your
+maximum and minimum values should correspond in type.  So you should
+pass in a `int*`, `*float` or `*double` by taking the address of a
+local variable.
 
 Lastly, it is usually necessary to be informed when input signal
 values change.  This is done by providing a function to be called
@@ -214,38 +152,40 @@ that function during callback in `user_data`.
 An example of creating a "barebones" `int` scalar output signal with
 no unit, minimum, or maximum information:
 
-    mapper_signal outputA = mdev_add_output( dev, "/outA", 1, 'i', 0, 0, 0 );
+    mapper::Signal outputA = dev.add_output( "/outA", 1, 'i', 0, 0, 0 );
 
 An example of a `float` signal where some more information is provided:
 
     float minimum = 0.0f;
     float maximum = 5.0f;
-    mapper_signal sensor1_voltage = mdev_add_output( dev, "/sensor1", 1, 'f',
-                                                     "V", &minimum, &maximum );
+    mapper::Signal sensor1_voltage = mdev.add_output( "/sensor1", 1, 'f',
+                                                      "V", &minimum, &maximum );
 
 So far we know how to create a device and to specify an output signal
 for it.  To recap, let's review the code so far:
  
-    mapper_device my_sender = mdev_new( "test_sender", 9000, 0 );
-    mapper_signal sensor1_voltage = mdev_add_output( my_sender, "/sensor1",
-                                                     1, 'f', "V",
-                                                     &minimum, &maximum );
+    mapper::Device dev( "test_sender");
+    mapper::Signal sensor1_voltage = dev( "/sensor1", 1, 'f', "V",
+                                          &minimum, &maximum );
     
     while ( !done ) {
-        mdev_poll( my_sender, 50 );
+        dev.poll( 50 );
         ... do stuff ...
         ... update signals ...
     }
-    
-    mdev_free( my_sender );
 
-Note that although you have a pointer to the mapper_signal structure,
-which was retuned by `mdev_add_output`, its memory is "owned" by the
-device.  In other words, you should not worry about freeing its
-memory - this will happen automatically when the device is destroyed.
 It is possible to retrieve a device's inputs or outputs by name or by
-index at a later time using the functions
-`mdev_get_<input/output>_by_<name/index>`.
+index at a later time using the functions `dev.input()` or `dev.output()`
+with either the signal name or index as an argument. The functions
+`dev.inputs()` and `dev.outputs()` return an object of type  `mapper::Signal::Iterator` which can be used to retrieve all of the
+input/output signals belonging to a particular device:
+
+    std::cout << "Signals belonging to " << dev.name() << std::endl;
+
+    mapper::Signal::Iterator iter = dev.inputs().begin();
+    for (; iter != dev.inputs().end(); iter++) {
+        std::cout << "input: " << (*iter).full_name() << std::endl;
+    }
 
 Updating signals
 ----------------
@@ -258,40 +198,33 @@ be a mouse-controlled GUI slider.  However it's getting the data, it
 must provide it to _libmapper_ so that it will be sent to other
 devices if that signal is mapped.
 
-This is accomplished by the `msig_update` function:
+This is accomplished by the `update()` function:
 
-    void msig_update( mapper_signal sig,
-                      void *value,
-                      int count,
-                      mapper_timetag_t timetag );
+    void sig.update( void *value,
+                     int count,
+                     mapper.Timetag timetag );
 
-As you can see, a `void*` pointer must be provided.  This must point
-to a data structure identified by the signal's `length` and `type`.
+The `count` and `timetag` arguments can be ommited, and the `update()`
+function is overloaded to accept scalars, arrays, and vectors as appropriate for the datatype and lengthof the signal in question.
 In other words, if the signal is a 10-vector of `int`, then `value`
-should point to the first item in a C array of 10 `int`s.  If it is a
-scalar `float`, it should be provided with the address of a `float`
+should point to an array or vector of 10 `int`s.  If it is a
+scalar `float`, it should be provided with a `float`
 variable. The `count` argument allows you to specify the number of
 value samples that are being updated - for now we will set this to 1.
 Lastly the `timetag` argument allows you to specify a time associated with
 the signal update. If your value update was generated locally, or if your
 program does not have access to upstream timing information (e.g., from a
-microcontroller sampling sensor values), you can use the macro `MAPPER_NOW`
+microcontroller sampling sensor values), you can omit the argument
 and libmapper will tag the update with the current time.
 
-To simplify things even further, a short-hand is provided for scalar signals of particular types:
-
-    void msig_update_int( mapper_signal sig, int value );
-
-    void msig_update_float( mapper_signal sig, float value );
-
 So in the "sensor 1 voltage" example, assuming in "do stuff" we have
 some code which reads sensor 1's value into a float variable called
 `v1`, the loop becomes:
 
     while ( !done ) {
-        mdev_poll( my_device, 50 );
+        dev.poll( 50 );
         float v1 = read_sensor_1();
-        msig_update_float( sensor1_voltage, v1 );
+        sensor1_voltage.update( v1 );
     }
 
 This is about all that is needed to expose sensor 1's voltage to the
@@ -340,11 +273,11 @@ Now that we know how to create a sender, it would be useful to also
 know how to receive signals, so that we can create a sender-receiver
 pair to test out the provided mapping functionality.
 
-As mentioned above, the `mdev_add_input` function takes an optional
+As mentioned above, the `add_input()` function takes an optional
 `handler` and `user_data`.  This is a function that will be called
 whenever the value of that signal changes.  To create a receiver for a
 synthesizer parameter "pulse width" (given as a ratio between 0 and
-1), specify a handler when calling `mdev_add_input`.  We'll imagine
+1), specify a handler when calling `add_input()`.  We'll imagine
 there is some C++ synthesizer implemented as a class `Synthesizer`
 which has functions `setPulseWidth()` which sets the pulse width in a
 thread-safe manner, and `startAudioInBackground()` which sets up the
@@ -352,14 +285,13 @@ audio thread.
 
 Create the handler function, which is fairly simple,
 
-    void pulsewidth_handler ( mapper_signal msig,
-                              mapper_db_signal props,
+    void pulsewidth_handler ( mapper::Signal msig,
                               int instance_id,
                               void *value,
                               int count,
-                              mapper_timetag_t *tt )
+                              mapper::Timetag tt )
     {
-        Synthesizer *s = (Synthesizer*) props->user_data;
+        Synthesizer *s = (Synthesizer*) msig.properties()->user_data;
         s->setPulseWidth( *(float*)v );
     }
 
@@ -377,22 +309,19 @@ Then `main()` will look like,
         float min_pw = 0.0f;
         float max_pw = 1.0f;
         
-        mapper_device my_receiver = mdev_new( "test_receiver", 9000, 0 );
+        mapper::Device my_receiver( "test_receiver" );
         
-        mapper_signal synth_pulsewidth =
-            mdev_add_input( my_receiver, "/synth/pulsewidth",
-                            1, 'f', 0, &min_pw, &max_pw,
-                            pulsewidth_handler, &synth );
+        mapper::Signal synth_pulsewidth =
+            dev.add_input( "/synth/pulsewidth", 1, 'f', 0, &min_pw,
+                           &max_pw, pulsewidth_handler, &synth );
         
         while ( !done )
-            mdev_poll( my_receiver, 50 );
-        
-        mdev_free( my_receiver );
+            dev.poll( 50 );
     }
 
 Working with timetags
 =====================
-_libmapper_ uses the `mapper_timetag_t` data structure to store
+_libmapper_ uses the `Timetag` data structure to store
 [NTP timestamps](http://en.wikipedia.org/wiki/Network_Time_Protocol#NTP_timestamps).
 For example, the handler function called when a signal update is received
 contains a `timetag` argument.  This argument indicates the time at
@@ -400,19 +329,18 @@ which the source signal was _sampled_ (in the case of sensor signals)
 or _generated_ (in the case of sequenced or algorithimically-generated
 signals).
 
-When updating output signals, using the functions `msig_update_int()`
-or `msig_update_float()` will automatically label the outgoing signal
+When updating output signals, using the function `update()` without a
+timetag argument will automatically label the outgoing signal
 update with the current time. In cases where the update should more
-properly be labeled with another time, this can be accomplished with
-the function `msig_update()`.  This timestamp should only be
+properly be labeled with another time, this can be accomplished by
+including the timetag argument.  This timestamp should only be
 overridden if your program has access to a more accurate measurement
 of the real time associated with the signal update, for example if
 you are writing a driver for an outboard sensor system that provides
-the sampling time.  Otherwise the constant `MAPPER_NOW` can be used
-as the timetag argument to cause libmapper to provide the current time.
+the sampling time.
 
 _libmapper_ also provides helper functions for getting the current
-device-time, setting the value of a `mapper_timetag_t` from other
+device-time, setting the value of a `Timetag` from other
 representations, and comparing or copying timetags.  Check the API
 documentation for more information.
 
@@ -442,18 +370,17 @@ The important qualities of signal instances in _libmapper_ are:
 All signals possess one instance by default. If you would like to reserve
 more instances you can use:
 
-    msig_reserve_instances(mapper_signal sig, int num)
+    sig.reserve_instances( int num )
 
-After reserving instances you can update a specific instance:
+After reserving instances you can update a specific instance, for example:
 
-    msig_update_instance(mapper_signal sig,
-    					 int instance_id,
-    					 void *value,
-    					 int count,
-    					 mapper_timetag_t timetag)
+    sig.update_instance( int instance_id,
+                         void *value,
+                         int count,
+                         Timetag timetag)
 
 All of the arguments except one should be familiar from the
-documentation of `msig_update()` presented earlier.
+documentation of `update()` presented earlier.
 The `instance_id` argument does not have to be considered as an array
 index - it can be any integer that is convenient for labelling your
 instance. _libmapper_ will internally create a map from your id label
@@ -465,16 +392,15 @@ You might have noticed earlier that the handler function called when
 a signal update is received has a argument called `instance_id`. Here
 is the function prototype again:
 
-    void mapper_signal_update_handler(mapper_signal msig,
-                                      mapper_db_signal props,
+    void mapper_signal_update_handler(mapper::Signal msig,
                                       int instance_id,
                                       void *value,
                                       int count,
-                                      mapper_timetag_t *tt);
+                                      mapper::Timetag tt);
 
 Under normal usage, this argument will have a value (0 <= n <= num_instances)
 and can be used as an array index. Remember that you will need to reserve
-instances for your input signal using `msig_reserve_instance()` if you
+instances for your input signal using `sig.reserve_instances()` if you
 want to receive instance updates.
 
 Instance Stealing
@@ -485,8 +411,7 @@ the receiver signal, the _instance allocation mode_ can be set for an
 input signal to set an action to take in case all allocated instances are in
 use and a previously unseen instance id is received. Use the function:
 
-    void msig_set_instance_allocation_mode(mapper_signal sig,
-                                           mapper_instance_allocation_type mode);
+    sig.set_instance_allocation_mode( instance_allocation_type mode );
 
 The argument `mode` can have one of the following values:
 
@@ -499,25 +424,23 @@ The argument `mode` can have one of the following values:
 If you want to use another method for determining which active instance
 to release (e.g. the sound with the lowest volume), you can create an `instance_event_handler` for the signal and write the method yourself:
 
-    void my_handler(mapper_signal msig,
-                    mapper_db_signal props,
+    void my_handler(mapper::Signal msig,
                     int instance_id,
                     msig_instance_event_t event,
-                    mapper_timetag_t *tt)
+                    mapper::Timetag tt)
     {
         // user code chooses which instance to release
         int id = choose_instance_to_release(msig);
 
-        msig_release_instance(msig, id, *tt);
+        msig.release_instance(id, tt);
     }
 
 For this function to be called when instance stealing is necessary, we
 need to register it for `IN_OVERFLOW` events:
 
-    msig_set_instance_event_callback(msig,
-                                     my_handler,
-                                     IN_OVERFLOW,
-                                     *user_context);
+    msig.set_instance_event_callback( my_handler,
+                                      IN_OVERFLOW,
+                                      *user_context);
 
 Publishing metadata
 ===================
@@ -541,42 +464,18 @@ any OSC-compatible type.  (So, numbers and strings, etc.)
 
 The property interface is through the functions,
 
-    void mdev_set_property( mapper_device dev,
-                            const char *property,
-                            lo_type type,
-                            lo_arg *value );
+    void dev.properties.set( <name>, <value> );
 
-    void msig_set_property( mapper_signal sig,
-                            const char *property,
-                            lo_type type,
-                            lo_arg *value );
+    void sig.set_property( <name>, <value> );
 
-As you can see, _libmapper_ reuses the `lo_arg` union from the _liblo_
-OSC library, which can be used to hold any OSC-compatible value.  The
-type of the `value` argument is specified by `type`, and can be any
-`lo_type` value; floats are `'f'` or `LO_FLOAT`, 32-bit integers are
-`'i'` or `LO_INT32`, and strings are `'s'` or `LO_STRING`, but you
-should consult the _liblo_ documentation for more information.
+The `<value>` arguments can be a scalar, array or std::vector of type
+`int`, `float`, `double`, or `char*`.
 
 For example, to store a `float` indicating the X position of a device,
 you can call it like this:
 
-    lo_arg x;
-    x.f = 12.5;
-    mdev_set_property( my_device, "x", 'f', &x );
-
-In practice it is safe to cast to `lo_arg*`:
-
-    float x = 12.5;
-    mdev_set_property( my_device, "x", 'f', (lo_arg*)&x );
-
-To specify strings, it is necessary to perform such a cast, since the
-`lo_arg*` you provide should actually point to the beginning of the
-string:
-
-    char *sensingMethod = "resistive";
-    msig_set_property( sensor1, "sensingMethod",
-                       's', (lo_arg*)sensingMethod );
+    dev.set_property( "x", 12.5f );
+    sig.set_property( "sensingMethod", "resistive" );
 
 In general you can use any property name not already in use by the
 device or signal data structure.  Reserved words for signals are:
@@ -589,14 +488,12 @@ for devices, they are:
 
 By the way, if you query or set signal properties using these
 keywords, you will get or modify the same information that is
-available directly from the `mapper_db_signal` data structure.
+available directly from the `mapper::DeviceProps` data structure.
 Therefore this can provide a unified string-based method for accessing
 any signal property:
 
-    mapper_db_signal *props = msig_properties( sensor1 );
-    lo_type type;
-    const lo_arg *value;
-    mapper_db_signal_property_lookup( props, "sensingMethod", &type, &value );
+    mapper::SignalProps props = sig.properties();
+    mapper::Property = props.get("sensingMethod");
 
 Primarily this is an interface meant for network monitors, but may
 come in useful for an application implementing a device.
diff --git a/doc/tutorials/tutorial_python.md b/doc/tutorials/tutorial_java.md
similarity index 59%
copy from doc/tutorials/tutorial_python.md
copy to doc/tutorials/tutorial_java.md
index 47ae735..24201f6 100644
--- a/doc/tutorials/tutorial_python.md
+++ b/doc/tutorials/tutorial_java.md
@@ -3,14 +3,12 @@ Getting started
 
 Since _libmapper_ uses GNU autoconf, getting started with the library
 is the same as any other library on Linux; use `./configure` and then
-`make` to compile it.  You'll need `swig` available if you want to
-compile the Python bindings.  On Mac OS X, we provide a precompiled
-Framework bundle for 32- and 64-bit Intel platforms, so using it with
-XCode should be a matter of including it in your project.
+`make` to compile it.  You'll need the `Java Developer Kit (JDK)` 
+available if you want to compile the Java bindings.
 
 Once you have libmapper installed, it can be imported into your program:
 
-    import mapper
+    import Mapper.*;
 
 Overview of the API organization
 ================================
@@ -26,14 +24,13 @@ The libmapper API is is divided into the following sections:
 * Links database
 * Monitors
 
-For this tutorial, the only sections to pay attention to are Devices
-and Signals.  Admins is reserved for providing custom networking
-configurations, but in general you don't need to worry about it.
+For this tutorial, the only sections to pay attention to are **Devices**
+and **Signals**.  Use of **Admins** is reserved for providing custom networking configurations, and in general you don't need to worry about it.
 
-Monitor and the various database modules are used to keep track of
+**Monitors** and the various **database** modules are used to keep track of
 what devices, signals and connections are on the network.  Devices do
 not need to worry about this.  It is used mainly for creating user
-interfaces for mapping design and will also not be covered here.
+interfaces for mapping design and will also not be covered in this tutorial.
 
 Devices
 =======
@@ -59,7 +56,7 @@ interface to use.
 
 An example of creating a device:
 
-    dev = mapper.device( "my_device" )
+    final Mapper.Device dev = new Mapper.Device( "my_device" );
 
 Polling the device
 ------------------
@@ -73,25 +70,19 @@ The device lifecycle looks like this, in terrible ASCII diagram art:
 In other words, after a device is created, it must be continuously
 polled during its lifetime.
 
-The polling is necessary for several reasons: to respond to requests
-on the admin bus; to check for incoming signals; to update outgoing
-signals.  Therefore even a device that does not have signals must be
-polled.  The user program must organize to have a timer or idle
-handler which can poll the device often enough.  The polling interval is
-not extremely sensitive, but should be 100 ms or less.  The more often
-it is polled, the faster it can handle incoming and outgoing
+The polling is necessary for several reasons: to respond to administrative messages; to check for incoming signals.  Therefore even a device that does not have signals must be polled.  The user program must organize to have a timer or idle handler which can poll the device often enough.  The polling interval is not extremely sensitive, but should be 100 ms or less.  The more often it is polled, the faster it can handle incoming and outgoing
 signals.
 
-The `poll` function can be blocking or non-blocking, depending on
+The `poll()` function can be blocking or non-blocking, depending on
 how you want your application to behave.  It takes a number of
 milliseconds during which it should do some work, or 0 if it should
 check for any immediate actions and then return without waiting:
 
-    int count = dev.poll( int block_ms )
+    int count = dev.poll( int block_ms );
 
 An example of calling it with non-blocking behaviour:
 
-    dev.poll( 0 )
+    dev.poll( 0 );
 
 If your polling is in the middle of a processing function or in
 response to a GUI event for example, non-blocking behaviour is
@@ -135,9 +126,9 @@ Creating a signal
 A signal requires a bit more information than a device, much of which
 is optional:
 
-* a name for the signal (must be unique within a devices inputs or outputs)
+* a name for the signal (must be unique within a device's inputs or outputs)
 * the signal's vector length
-* the signal's data type expressed as a character 'i', 'f', 'd'
+* the signal's data type expressed as a character 'i', 'f' or 'd'
 * the signal's unit (optional)
 * the signal's minimum value (optional)
 * the signal's maximum value (optional)
@@ -148,63 +139,70 @@ for input signals there is an additional argument:
 
 examples:
 
-    sig_in = dev.add_input( "/my_input", 1, 'f', "m/s", -10, 10, h )
+    Mapper.Device.Signal in = dev.addInput( "/my_input", 1, 'f', "m/s",
+                                             new PropertyValue(-10.f),
+                                             null, new InputListener() {
+            public void onInput( Mapper.Device.Signal sig,
+                                 int instanceID,
+                                 float[] value,
+                                 Mapper.TimeTag tt) {
+                System.out.println("got input for signal "+sig.name);
+            }});
 
-    sig_out = dev.add_output( "/my_output", 4, 'i', None, 0, 1000 )
+    Mapper.Device.Signal out = dev.addOutput( "/my_output", 4, 'i', null, 0, 1000 )
 
 The only _required_ parameters here are the signal "length", its name,
 and data type.  Signals are assumed to be vectors of values, so for
-usual single-valued signals, a length of 1 should be specified.  A
-signal name should start with "/", as this is how it is represented in
-the OSC address.  (One will be added if you forget to do this.)
-Finally, supported types are currently 'i' or 'f' for `int` or
-`float` values, respectively.
+usual single-valued signals, a length of 1 should be specified.
+Finally, supported types are currently 'i', 'f' or 'd' for `int`,
+`float` or `double` values, respectively.
 
 The other parameters are not strictly required, but the more
 information you provide, the more the mapper can do some things
-automatically.  For example, if `minimum` and `maximum` are provided,
-it will be possible to create linear-scaled connections very quickly.
-If `unit` is provided, the mapper will be able to similarly figure out
-a linear scaling based on unit conversion. (Centimeters to inches for
-example.)^[Currently automatic unit-based scaling is not a supported
-feature, but will be added in the future.  You can take advantage of
-this future development by simply providing unit information whenever
+automatically.  For example, if the `minimum` and `maximum` properties
+are provided, it will be possible to create linear-scaled connections
+very quickly. If `unit` is provided, the mapper will be able to similarly figure out a linear scaling based on unit conversion. (Centimeters to
+inches for example.)^[Currently automatic unit-based scaling is not a supported feature, but will be added in the future.  You can take advantage of this future development by simply providing unit information whenever
 it is available.  It is also helpful documentation for users.]
 
 Lastly, it is usually necessary to be informed when input signal
 values change.  This is done by providing a function to be called
 whenever its value is modified by an incoming message.  It is passed
-in the `handler` parameter.
+in the `InputListener` parameter.
 
-An example of creating a "barebones" `int` scalar output signal with
+An example of creating a "barebones" integer scalar output signal with
 no unit, minimum, or maximum information:
 
-    outA = dev.add_output( "/outA", 1, 'i', None, None, None )
-
-or omitting some arguments:
-
-    outA = dev.add_output( "/outA", 1, 'i' )
+    Mapper.Device.Signal outA = dev.addOutput( "/outA", 1, 'i', null, null, null );
 
 An example of a `float` signal where some more information is provided:
 
-    sensor1_voltage = dev.add_output( "/sensor1", 1, 'f', "V", 0.0, 5.0 )
+    Mapper.Device.Signal sensor1_voltage = dev.addOutput( "/sensor1", 1,     'f',
+                                                                      "V", 0.0, 5.0 )
 
 So far we know how to create a device and to specify an output signal
 for it.  To recap, let's review the code so far:
 
-    import mapper
-
-    dev = mapper.device( "test_sender" )
-    sensor1_voltage = dev.add_output( "/sensor1", 1, 'f', "V", 0.0, 5.0 )
-    
-    while 1:
-        dev.poll( 50 )
-        ... do stuff ...
-        ... update signals ...
+    import Mapper.*;
+
+    class test {
+        public static void main() {
+            final Mapper.Device dev = new Mapper.Device( "testDevice" );
+            Mapper.Device.Signal sensor1 =
+                dev.addOutput( "sensor1", 1, 'f', "V",
+                               new PropertyValue( 0.f ),
+                               new PropertyValue( 5.f ) );
+            while ( 1 ) {
+                dev.poll(50);
+                ... do stuff ...
+                ... update signals ...
+            }
+        }
+    }
 
 It is possible to retrieve a device's inputs or outputs by name or by
-index at a later time using the functions
-`get_<input/output>_by_<name/index>`.
+index at a later time using the functions `getInput()` and `getOutput()`,
+passing either the signal name or its index as an argument.
 
 Updating signals
 ----------------
@@ -217,18 +215,18 @@ be a mouse-controlled GUI slider.  However it's getting the data, it
 must provide it to _libmapper_ so that it will be sent to other
 devices if that signal is mapped.
 
-This is accomplished by the `update` function:
+This is accomplished by the `update()` function:
 
     <sig>.update( <value> )
 
-So in the "sensor 1 voltage" example, assuming in "do stuff" we have
-some code which reads sensor 1's value into a float variable called
-`v1`, the loop becomes:
+So in the "sensor 1 voltage" example, assuming we have some code which
+reads sensor 1's value into a float variable called `v1`, the loop becomes:
 
-    while 1:
-        dev.poll( 50 )
-        v1 = read_sensor_1()
-        sensor1_voltage.update( v1 )
+    while ( 1 ) {
+        dev.poll( 50 );
+        v1 = read_sensor_1();
+        sensor1.update( v1 );
+    }
 
 This is about all that is needed to expose sensor 1's voltage to the
 network as a mappable parameter.  The _libmapper_ GUI can now be used
@@ -240,14 +238,14 @@ Signal conditioning
 -------------------
 
 Most synthesizers of course will not know what to do with
-"voltage"--it is an electrical property that has nothing to do with
+"voltage" – it is an electrical property that has nothing to do with
 sound or music.  This is where _libmapper_ really becomes useful.
 
 Scaling or other signal conditioning can be taken care of _before_
 exposing the signal, or it can be performed as part of the mapping.
-Since the end user can demand any mathematical operation be performed
-on the signal, he can perform whatever mappings between signals as he
-wishes.
+Since end users can demand any mathematical operation be performed
+on the signal, they can perform whatever mappings between signals they
+wish.
 
 As a developer, it is therefore your job to provide information that
 will be useful to the end user.
@@ -277,85 +275,78 @@ Now that we know how to create a sender, it would be useful to also
 know how to receive signals, so that we can create a sender-receiver
 pair to test out the provided mapping functionality.
 
-As mentioned above, the `add_input` function takes an optional
-`handler`.  This is a function that will be called whenever the value
+As mentioned above, the `add_input()` function takes an optional
+`InputListener`.  This is a function that will be called whenever the value
 of that signal changes.  To create a receiver for a synthesizer
 parameter "pulse width" (given as a ratio between 0 and 1), specify
 a handler when calling `add_input`.  We'll imagine there is some
-python synthesizer implemented as a class `synthesizer` which has
+Java synthesizer implemented as a class `Synthesizer` which has
 functions `setPulseWidth()` which sets the pulse width in a
 thread-safe manner, and `startAudioInBackground()` which sets up the
 audio thread.
 
-Let's use a real-world example using the
-[pyo DSP library for Python](http://code.google.com/p/pyo/) to create a
-simple synth consisting of one sine wave. For now, we will only worry
-about controlling one parameter: the frequency of the sine.
+We need to create a handler function for libmapper to update the synth:
 
-We need to create a handler function for libmapper to update the pyo synth:
-
-    def frequency_handler(sig, id, val, timetag):
-        try:
-            sine.setFreq( val )
-        except:
-            print 'exception'
-            print sig, val
+    InputListener freqHandler = new InputListener() {
+        public void onInput( Mapper.Device.Signal sig,
+                             int instanceId,
+                             float[] value,
+                             TimeTag tt ) {
+            setPulseWidth( value );
+        }}
 
 Then our program will look like this:
 
-    from pyo import *
-    import mapper
-
-    # Some pyo stuff
-    synth = Server().boot().start()
-    sine = Sine( freq=200, mul=0.5 ).out()
+    import Mapper.*;
 
-    def freq_handler( sig, id, val, timetag ):
-        try:
-            sine.setFreq( val )
-        except:
-            print 'exception'
-            print sig, val
+    # Some synth stuff
+    startAudioInBackground();
 
-    dev = mapper.device( 'pyo_example' )
-    dev.add_input( '/freq', 1, 'f', 'Hz', 20, 2000, freq_handler )
+    InputListener freqHandler = new InputListener() {
+        public void onInput( Mapper.Device.Signal sig,
+                             int instanceId,
+                             float[] value,
+                             Mapper.TimeTag tt ) {
+            setPulseWidth( value );
+        }}
 
-    while True:
-        dev.poll( 100 )
+    final Mapper.Device dev = new Mapper.Device( "mySynth" );
+    Mapper.Device.Signal pw = dev.addInput( "pulseWidth", 1, 'f', "Hz",
+                                            new PropertyValue( 0.f ),
+                                            new PropertyValue( 1.f ),
+                                            freqHandler );
 
-	synth.stop()
+    while (1) {
+        dev.poll( 100 );
+    }
 
-Alternately, we can simplify our code by using a `lambda function`
-instead of a separate handler:
+    synth.stop()
 
-    from pyo import *
-    import mapper
+Alternately, we can declare the InputListener as part of the `addInput()` function:
 
-    # Some pyo stuff
-    synth = Server().boot().start()
-    sine = Sine( freq=200, mul=0.5 ).out()
-
-    dev = mapper.device( 'pyo_example' )
-    dev.add_input( '/freq', 1, 'f', "Hz", 20, 2000, lambda s, i, f, t: sine.setFreq(f) )
-
-    while True:
-        dev.poll( 100 )
-
-	synth.stop()
+    Mapper.Device.Signal pw = dev.addInput( "pulseWidth", 1, 'f', "Hz",
+                                            new PropertyValue( 0.f ),
+                                            new PropertyValue( 1.f ),
+                                            new InputListener() {
+        public void onInput( Mapper.Device.Signal sig,
+                             int instanceId,
+                             float[] value,
+                             Mapper.TimeTag tt ) {
+            setPulseWidth( value );
+        }});
 
 
 Working with timetags
 =====================
-_libmapper_ uses the `mapper_timetag_t` data structure internally to store
-[NTP timestamps](http://en.wikipedia.org/wiki/Network_Time_Protocol#NTP_timestamps),
-but this value is represented using the `double` type in the python bindings.
+_libmapper_ uses the `TimeTag` class to store 
+[NTP timestamps](http://en.wikipedia.org/wiki/Network_Time_Protocol#NTP_timestamps) associated with signal updates.
 For example, the handler function called when a signal update is received
 contains a `timetag` argument.  This argument indicates the time at
 which the source signal was _sampled_ (in the case of sensor signals)
 or _generated_ (in the case of sequenced or algorithimically-generated
 signals).
 
-The `update` function for output signals is overloaded; calling the function
+The signal `update()` function for output signals is overloaded; calling the function
 without a timetag argument will automatically label the outgoing signal
 update with the current time. In cases where the update should more
 properly be labeled with another time, this can be accomplished by simply
@@ -368,7 +359,7 @@ the sampling time.
 _libmapper_ also provides helper functions for getting the current
 device-time:
 
-    now = <device>.now()
+    Mapper.TimeTag now = <device>.now();
 
 Working with signal instances
 =============================
@@ -396,22 +387,19 @@ The important qualities of signal instances in _libmapper_ are:
 All signals possess one instance by default. If you would like to reserve
 more instances you can use:
 
-    <sig>.reserve_instances( int num )
+    <sig>.reserveInstances( int num );
 
 After reserving instances you can update a specific instance:
 
-    <sig>.update_instance( int instance_id,
-                           <value> )
+    <sig>.updateInstance( int instanceId, <value> );
 
 or
 
-    <sig>.update_instance( int instance_id,
-                           <value>,
-                           double timetag )
+    <sig>.updateInstance( int instanceId, <value>, TimeTag tt );
 
 All of the arguments except one should be familiar from the
-documentation of `msig_update()` presented earlier.
-The `instance_id` argument does not have to be considered as an array
+documentation of `update()` presented earlier.
+The `instanceId` argument does not have to be considered as an array
 index - it can be any integer that is convenient for labelling your
 instance. _libmapper_ will internally create a map from your id label
 to one of the preallocated instance structures.
@@ -419,14 +407,17 @@ to one of the preallocated instance structures.
 Receiving instances
 -------------------
 You might have noticed earlier that the handler function called when
-a signal update is received has a argument called `id`. Here
-is the function prototype again:
+a signal update is received has a argument called `instanceId`. Here
+again is the function prototype for a float signal:
 
-	def frequency_handler( signal, id, value, timetag ):
+	new InputListener( Mapper.Device.Signal sig,
+	                   int instanceId,
+	                   float[] value,
+	                   Mapper.TimeTag tt );
 
-Under normal usage, the `id` argument will have a value (0 <= n <= num_instances)
+Under normal usage, the `instanceId` argument will have a value (0 <= n <= numInstances)
 and can be used as an array index. Remember that you will need to reserve
-instances for your input signal using `<sig>.reserve_instances()` if you
+instances for your input signal using `<sig>.reserveInstances()` if you
 want to receive instance updates.
 
 Instance Stealing
@@ -437,42 +428,46 @@ the receiver signal, the _instance allocation mode_ can be set for an
 input signal to set an action to take in case all allocated instances are in
 use and a previously unseen instance id is received. Use the function:
 
-    <sig>.set_instance_allocation_mode( mapper_instance_allocation_type mode );
+    <sig>.setInstanceAllocationMode( mode );
 
 The argument `mode` can have one of the following values:
 
-* `mapper.IN_UNDEFINED` Default value, in which no stealing of instances will occur;
-* `mapper.IN_STEAL_OLDEST` Release the oldest active instance and reallocate its
+* `Mapper.Signal.IN_UNDEFINED` Default value, in which no stealing of instances will occur;
+* `Mapper.Signal.IN_STEAL_OLDEST` Release the oldest active instance and reallocate its
   resources to the new instance;
-* `mapper.IN_STEAL_NEWEST` Release the newest active instance and reallocate its
+* `Mapper.Signal.IN_STEAL_NEWEST` Release the newest active instance and reallocate its
   resources to the new instance;
 
 If you want to use another method for determining which active instance
-to release (e.g. the sound with the lowest volume), you can create an `instance_event_handler` for the signal and write the method yourself:
-
-	def my_handler( sig, id, event, timetag ):
-        # user code chooses which instance to release
-        id = choose_instance_to_release( msig )
-
-        sig.release_instance( id, timetag )
+to release (e.g. the sound with the lowest volume), you can create an `instanceEventListener` for the signal and write the method yourself:
+
+    Mapper.instanceEventListener myHandler = new Mapper.instanceEventListener() {    
+        public void onEvent( Mapper.Device.Signal sig,
+                             int instanceId,
+                             int event,
+                             Mapper.TimeTag tt ) {
+            int id = chooseInstanceToRelease( sig ); // user function
+            sig.releaseInstance( id, tt );
+        }
+    }
 
 For this function to be called when instance stealing is necessary, we
-need to register it for `mapper.IN_OVERFLOW` events:
+need to register it for `Mapper.instanceEventListener.IN_OVERFLOW` events:
 
-    <sig>.set_instance_event_callback( my_handler,
-                                       mapper.IN_OVERFLOW )
+    <sig>.setInstanceEventCallback( myHandler,
+                                    Mapper.instanceEventListener.IN_OVERFLOW );
 
 
 Publishing metadata
 ===================
 
 Things like device names, signal units, and ranges, are examples of
-metadata--information about the data you are exposing on the network.
+metadata – information about the data you are exposing on the network.
 
 _libmapper_ also provides the ability to specify arbitrary extra
 metadata in the form of name-value pairs.  These are not interpreted
 by _libmapper_ in any way, but can be retrieved over the network.
-This can be used for instance to label a device with its loation, or
+This can be used for instance to label a device with its location, or
 to perhaps give a signal some property like "reliability", or some
 category like "light", "motor", "shaker", etc.
 
@@ -485,24 +480,25 @@ any OSC-compatible type.  (So, numbers and strings, etc.)
 
 The property interface is through the functions,
 
-    set_property( key, value )
+    <object>.setProperty( String key, PropertyValue value );
 
 where the value can any OSC-compatible type. This function can be called
 for devices or signals.
 
-For example, to store a `float` indicating the X position of a device
+For example, to store a `float vector` indicating the 2D position of a device
 `dev`, you can call it like this:
 
-    dev.set_property( "x", 12.5 )
+    dev.setProperty( "position", new PropertyValue( new float[] { 12.5f, 40.f } ) );
 
-To specify a string property of a signal:
+To specify a string property of a signal `sig`:
 
-    sig.set_property( "sensingMethod", "resistive" )
+    sig.setProperty( "sensingMethod", new PropertyValue( "resistive" ) );
 
 In general you can use any property name not already in use by the
 device or signal data structure.  Reserved words for signals are:
 
-    device_name, direction, length, max, min, name, type, unit, user_data
+    device_name, direction, length, max, maximum, min, minimum,
+    name, type, unit, user_data, value
 
 for devices, they are:
 
@@ -514,8 +510,9 @@ available directly from the `signal` data structure.
 Therefore this can provide a unified string-based method for accessing
 any signal property:
 
-    props = sig.get_properties()
-    sensingMethod = props[ 'sensingMethod' ]
+    props = sig.properties();
+    sensingMethod = props.property( "sensingMethod" );
+    minimum = props.property( "min" );
 
 Primarily this is an interface meant for network monitors, but may
 come in useful for an application implementing a device.
diff --git a/examples/py_tk_gui/Makefile.am b/examples/py_tk_gui/Makefile.am
index 7e2a412..a163510 100644
--- a/examples/py_tk_gui/Makefile.am
+++ b/examples/py_tk_gui/Makefile.am
@@ -1,4 +1,6 @@
 
+_CXX =  $(filter-out ccache,$(CXX))
+
 all-local: _pwm.$(PYEXT) $(top_builddir)/examples/pwm_synth/libpwm.la
 
 $(builddir)/%_wrap.cxx $(buildir)/%.py: %.i
@@ -8,7 +10,7 @@ $(builddir)/%_wrap.cxx $(buildir)/%.py: %.i
 # Don't interfere with distutils CFLAGS
 _%.$(PYEXT): $(builddir)/%_wrap.cxx
 	pwd
-	cd $(builddir) && env CFLAGS="" python setup.py build_ext
+	cd $(builddir) && env CFLAGS="" CXX="$(_CXX)" python setup.py build_ext
 	- at mv -v build/lib.*/$@ .
 	- at rm -rf build
 
diff --git a/include/Makefile.am b/include/Makefile.am
index 7b504c6..f935f26 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,4 +1,5 @@
 
 libmapperdir = $(includedir)/mapper- at MAJOR_VERSION@/mapper
 
-libmapper_HEADERS = mapper/mapper.h mapper/mapper_types.h mapper/mapper_db.h
+libmapper_HEADERS = mapper/mapper.h mapper/mapper_types.h mapper/mapper_db.h \
+                    mapper/mapper_cpp.h
diff --git a/include/mapper/mapper.h b/include/mapper/mapper.h
index ad34b66..2b48a50 100644
--- a/include/mapper/mapper.h
+++ b/include/mapper/mapper.h
@@ -82,7 +82,7 @@ typedef void mapper_signal_instance_event_handler(mapper_signal msig,
  *                  bundling multiple signal updates with the same
  *                  timetag. */
 void msig_update(mapper_signal sig, void *value,
-                 int count, mapper_timetag_t tt);
+                 int count, mapper_timetag_t timetag);
 
 /*! Update the value of a scalar signal of type int.
  *  This is a scalar equivalent to msig_update(), for when passing by
@@ -114,7 +114,7 @@ void msig_update_double(mapper_signal sig, double value);
  *                  May be 0.
  *  \return         A pointer to an array containing the value
  *                  of the signal, or 0 if the signal has no value. */
-void *msig_value(mapper_signal sig, mapper_timetag_t *tt);
+void *msig_value(mapper_signal sig, mapper_timetag_t *timetag);
 
 /*! Query the values of any signals connected via mapping connections.
  *  \param sig      A local output signal. We will be querying the remote
@@ -151,8 +151,8 @@ int msig_reserve_instances(mapper_signal sig, int num, int *ids, void **user_dat
  *                      libmapper will tag the value update with the current
  *                      time. See mdev_start_queue() for more information on
  *                      bundling multiple signal updates with the same timetag. */
-void msig_update_instance(mapper_signal sig, int instance_id,
-                          void *value, int count, mapper_timetag_t tt);
+void msig_update_instance(mapper_signal sig, int instance_id, void *value,
+                          int count, mapper_timetag_t timetag);
 
 /*! Release a specific instance of a signal by removing it from the list
  *  of active instances and adding it to the reserve list.
@@ -163,7 +163,7 @@ void msig_update_instance(mapper_signal sig, int instance_id,
  *                     See mdev_start_queue() for more information on
  *                     bundling multiple signal updates with the same timetag. */
 void msig_release_instance(mapper_signal sig, int instance_id,
-                           mapper_timetag_t tt);
+                           mapper_timetag_t timetag);
 
 /*! Remove a specific instance of a signal and free its memory.
  *  \param sig         The signal to operate on.
@@ -191,7 +191,7 @@ int msig_get_newest_active_instance(mapper_signal sig, int *instance_id);
  *                     of the signal instance, or 0 if the signal instance
  *                     has no value. */
 void *msig_instance_value(mapper_signal sig, int instance_id,
-                          mapper_timetag_t *tt);
+                          mapper_timetag_t *timetag);
 
 /*! Copy group/routing data for sharing an instance abstraction
  *  between multiple signals.
@@ -368,8 +368,30 @@ mapper_signal mdev_add_input(mapper_device dev, const char *name,
                              mapper_signal_update_handler *handler,
                              void *user_data);
 
+/*! Add an input signal with multiple instances to a mapper device.
+ *  Values and strings pointed to by this call (except user_data) will be copied.
+ *  For minimum and maximum, actual type must correspond to 'type'.
+ *  If type='i', then int*; if type='f', then float*.
+ *  \param dev           The device to add a signal to.
+ *  \param name          The name of the signal.
+ *  \param length        The length of the signal vector, or 1 for a scalar.
+ *  \param type          The type fo the signal value.
+ *  \param unit          The unit of the signal, or 0 for none.
+ *  \param minimum       Pointer to a minimum value, or 0 for none.
+ *  \param maximum       Pointer to a maximum value, or 0 for none.
+ *  \param num_instances Number of instances to reserve.
+ *  \param handler       Function to be called when the value of the
+ *                       signal is updated.
+ *  \param user_data     User context pointer to be passed to handler. */
+mapper_signal mdev_add_poly_input(mapper_device dev, const char *name,
+                                  int length, char type, const char *unit,
+                                  void *minimum, void *maximum,
+                                  int num_instances,
+                                  mapper_signal_update_handler *handler,
+                                  void *user_data);
+
 /*! Add an output signal to a mapper device.  Values and strings
- *  pointed to by this call will be copied.
+ *  pointed to by this call (except user_data) will be copied.
  *  For minimum and maximum, actual type must correspond to 'type'.
  *  If type='i', then int*; if type='f', then float*.
  *  \param dev     The device to add a signal to.
@@ -383,6 +405,23 @@ mapper_signal mdev_add_output(mapper_device dev, const char *name,
                               int length, char type, const char *unit,
                               void *minimum, void *maximum);
 
+/*! Add an output signal with multiple instances to a mapper device.
+ *  Values and strings pointed to by this call (except user_data) will be copied.
+ *  For minimum and maximum, actual type must correspond to 'type'.
+ *  If type='i', then int*; if type='f', then float*.
+ *  \param dev     The device to add a signal to.
+ *  \param name    The name of the signal.
+ *  \param length  The length of the signal vector, or 1 for a scalar.
+ *  \param type    The type fo the signal value.
+ *  \param unit    The unit of the signal, or 0 for none.
+ *  \param num_instances Number of instances to reserve.
+ *  \param minimum Pointer to a minimum value, or 0 for none.
+ *  \param maximum Pointer to a maximum value, or 0 for none. */
+mapper_signal mdev_add_poly_output(mapper_device dev, const char *name,
+                                   int length, char type, const char *unit,
+                                   void *minimum, void *maximum,
+                                   int num_instances);
+
 /* Remove a device's input signal.
  * \param dev The device to remove a signal from.
  * \param sig The signal to remove. */
@@ -582,8 +621,8 @@ double mdev_get_clock_offset(mapper_device md);
  *  connection. */
 typedef enum {
     MDEV_LOCAL_ESTABLISHED,
-    MDEV_LOCAL_DESTROYED,
     MDEV_LOCAL_MODIFIED,
+    MDEV_LOCAL_DESTROYED,
 } mapper_device_local_action_t;
 
 /*! Function to call when a local device link is established or
@@ -662,6 +701,7 @@ typedef enum {
     MDB_MODIFY,
     MDB_NEW,
     MDB_REMOVE,
+    MDB_UNRESPONSIVE,
 } mapper_db_action_t;
 
 /***** Devices *****/
@@ -1211,7 +1251,7 @@ mapper_db_link_t **mapper_db_get_links_by_dest_device_name(
 mapper_db_link mapper_db_get_link_by_src_dest_names(mapper_db db,
     const char *src_device_name, const char *dest_device_name);
 
-/*! Return the list of links for a given source name.
+/*! Return the list of links that touch devices in both src and dest lists.
  *  \param db The database to query.
  *  \param src_device_list  Double-pointer to the first item in a list
  *                          returned from a previous database query.
@@ -1281,23 +1321,29 @@ int mapper_db_link_property_lookup(mapper_db_link link,
        general, the monitor interface is useful for building GUI
        applications to control the network. */
 
-typedef enum {
-    AUTOREQ_SIGNALS     = 0x01,
-    AUTOREQ_LINKS       = 0x02,
-    AUTOREQ_CONNECTIONS = 0x04,
-    AUTOREQ_ALL         = 0xFF
-} mapper_monitor_autoreq_mode_t;
+/*! Bit flags for coordinating monitor metadata subscriptions. Subsets of
+ *  device information must also include SUB_DEVICE. */
+#define SUB_DEVICE                  0x01
+#define SUB_DEVICE_INPUTS           0x03
+#define SUB_DEVICE_OUTPUTS          0x05
+#define SUB_DEVICE_SIGNALS          0x07 // SUB_DEVICE_INPUTS & SUB_DEVICE_OUTPUTS
+#define SUB_DEVICE_LINKS_IN         0x09
+#define SUB_DEVICE_LINKS_OUT        0x11
+#define SUB_DEVICE_LINKS            0x19 // SUB_DEVICE_LINKS_IN & SUB_DEVICE_LINKS_OUT
+#define SUB_DEVICE_CONNECTIONS_IN   0x21
+#define SUB_DEVICE_CONNECTIONS_OUT  0x41
+#define SUB_DEVICE_CONNECTIONS      0x61 // SUB_DEVICE_CONNECTIONS_IN & SUB_DEVICE_CONNECTION_OUT
+#define SUB_DEVICE_ALL              0xFF
 
 /*! Create a network monitor.
- *  \param admin    A previously allocated admin to use.  If 0, an
- *                  admin will be allocated for use with this monitor.
- *  \param flags    Sets whether the monitor should automatically
- *                  request information about signals, links, and
- *                  connections when it encounters a previously-unseen
- *                  device.
+ *  \param admin               A previously allocated admin to use.  If 0, an
+ *                             admin will be allocated for use with this monitor.
+ *  \param autosubscribe_flags Sets whether the monitor should automatically
+ *                             subscribe to information about signals, links,
+ *                             and connections when it encounters a
+ *                             previously-unseen device.
  *  \return The new monitor. */
-mapper_monitor mapper_monitor_new(mapper_admin admin,
-                                  mapper_monitor_autoreq_mode_t flags);
+mapper_monitor mapper_monitor_new(mapper_admin admin, int autosubscribe_flags);
 
 /*! Free a network monitor. */
 void mapper_monitor_free(mapper_monitor mon);
@@ -1313,102 +1359,54 @@ int mapper_monitor_poll(mapper_monitor mon, int block_ms);
  *  long as the monitor remains alive. */
 mapper_db mapper_monitor_get_db(mapper_monitor mon);
 
-/*! Request that all devices report in. */
-int mapper_monitor_request_devices(mapper_monitor mon);
-
-/*! Request properties for specific device. */
-int mapper_monitor_request_device_info(
-    mapper_monitor mon, const char* name);
-
-/*! Request signals for specific device. */
-int mapper_monitor_request_signals_by_device_name(
-    mapper_monitor mon, const char* name);
+/*! Set the timeout in seconds after which a monitor will declare a device
+ *  "unresponsive". Defaults to ADMIN_TIMEOUT_SEC.
+ *  \param mon      The monitor to use.
+ *  \param timeout  The timeout in seconds. */
+void mapper_monitor_set_timeout(mapper_monitor mon, int timeout);
+
+/*! Get the timeout in seconds after which a monitor will declare a device
+ *  "unresponsive". Defaults to ADMIN_TIMEOUT_SEC.
+ *  \param mon      The monitor to use.
+ *  \return The current timeout in seconds. */
+int mapper_monitor_get_timeout(mapper_monitor mon);
+
+/*! Remove unresponsive devices from the database.
+ *  \param mon         The monitor to use.
+ *  \param timeout_sec The number of seconds a device must have been
+ *                     unresponsive before removal.
+ *  \param quiet       1 to disable callbacks during db flush, 0 otherwise. */
+void mapper_monitor_flush_db(mapper_monitor mon, int timeout_sec, int quiet);
 
-/*! Request output signals for specific device. */
-int mapper_monitor_request_output_signals_by_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request input signals for specific device. */
-int mapper_monitor_request_input_signals_by_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request an indexed subset of signals for specific device. */
-int mapper_monitor_request_signal_range_by_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-
-/*! Request an indexed subset of output signals for specific device. */
-int mapper_monitor_request_output_signal_range_by_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-
-/*! Request an indexed subset of input signals for specific device. */
-int mapper_monitor_request_intput_signal_range_by_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-
-/*! Request signals for specific device in measured batches. */
-int mapper_monitor_batch_request_signals_by_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Request output signals for specific device in measured batches. */
-int mapper_monitor_batch_request_output_signals_by_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Request input signals for specific device in measured batches. */
-int mapper_monitor_batch_request_input_signals_by_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Request links for specific device. */
-int mapper_monitor_request_links_by_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request outgoing links for specific device. */
-int mapper_monitor_request_links_by_src_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request incoming links for specific device. */
-int mapper_monitor_request_links_by_dest_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request connections for specific device. */
-int mapper_monitor_request_connections_by_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request outgoing connections for specific device. */
-int mapper_monitor_request_connections_by_src_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request incoming connections for specific device. */
-int mapper_monitor_request_connections_by_dest_device_name(
-    mapper_monitor mon, const char* name);
-
-/*! Request an indexed subset of connections for specific device. */
-int mapper_monitor_request_connection_range_by_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-    
-/*! Request an indexed subset of outgoing connections for specific device. */
-int mapper_monitor_request_connection_range_by_src_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-
-/*! Request an indexed subset of incoming connections for specific device. */
-int mapper_monitor_request_connection_range_by_dest_device_name(
-    mapper_monitor mon, const char* name, int start_index, int stop_index);
-
-/*! Request connections for specific device in measured batches. */
-int mapper_monitor_batch_request_connections_by_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Request outgoing connections for specific device in measured batches. */
-int mapper_monitor_batch_request_connections_by_src_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Request incoming connections for specific device in measured batches. */
-int mapper_monitor_batch_request_connections_by_dest_device_name(
-    mapper_monitor mon, const char* name, int batch_size);
-
-/*! Sets whether the monitor should automatically make requests for
+/*! Request that all devices report in. */
+void mapper_monitor_request_devices(mapper_monitor mon);
+
+/*! Subscribe to information about a specific device.
+ *  \param mon             The monitor to use.
+ *  \param device_name     The name of the device of interest.
+ *  \param subscribe_flags Bitflags setting the type of information of interest.
+ *                         Can be a combination of SUB_DEVICE, SUB_DEVICE_INPUTS,
+ *                         SUB_DEVICE_OUTPUTS, SUB_DEVICE_SIGNALS,
+ *                         SUB_DEVICE_LINKS_IN, SUB_DEVICE_LINKS_OUT,
+ *                         SUB_DEVICE_LINKS, SUB_DEVICE_CONNECTIONS_IN,
+ *                         SUB_DEVICE_CONNECTIONS_OUT, SUB_DEVICE_CONNECTIONS,
+ *                         or simply SUB_DEVICE_ALL for all information.
+ *  \param timeout         The length in seconds for this subscription. If set
+ *                         to -1, libmapper will automatically renew the
+ *                         subscription until the monitor is freed or this
+ *                         function is called again. */
+void mapper_monitor_subscribe(mapper_monitor mon, const char *device_name,
+                              int subscribe_flags, int timeout);
+
+/*! Unsubscribe from information about a specific device.
+ *  \param mon             The monitor to use.
+ *  \param device_name     The name of the device of interest. */
+void mapper_monitor_unsubscribe(mapper_monitor mon, const char *device_name);
+
+/*! Sets whether the monitor should automatically subscribe to
  *  information on signals, links, and connections when it encounters
  *  a previously-unseen device.*/
-void mapper_monitor_autorequest(mapper_monitor mon,
-                                mapper_monitor_autoreq_mode_t flags);
+void mapper_monitor_autosubscribe(mapper_monitor mon, int autosubscribe_flags);
 
 /*! Interface to add a link between two devices.
  *  \param mon            The monitor to use for sending the message.
@@ -1490,7 +1488,7 @@ void mdev_now(mapper_device dev,
               mapper_timetag_t *tt);
 
 /*! Initialize a timetag to the current mapping network time.
- *  \param dev  The device whose time we are asking for.
+ *  \param mon  The monitor whose time we are asking for.
  *  \param tt   A previously allocated timetag to initialize. */
 void mapper_monitor_now(mapper_monitor mon,
                         mapper_timetag_t *tt);
@@ -1502,20 +1500,28 @@ void mapper_monitor_now(mapper_monitor mon,
 double mapper_timetag_difference(mapper_timetag_t a, mapper_timetag_t b);
 
 /*! Add seconds to a given timetag.
- *  \param timetag  A previously allocated timetag to augment.
+ *  \param tt       A previously allocated timetag to augment.
  *  \param addend   An amount in seconds to add. */
 void mapper_timetag_add_seconds(mapper_timetag_t *tt, double addend);
 
-/*! Return value of mapper_timetag as a double-precision floating point value. */
+/*! Return value of mapper_timetag as a double-precision floating point value.
+ *  \param tt   The timetag to read.
+ *  \return     The timetag value in seconds. */
 double mapper_timetag_get_double(mapper_timetag_t tt);
 
-/*! Set value of a mapper_timetag from an integer value. */
+/*! Set value of a mapper_timetag from an integer value.
+ *  \param tt       The timetag to set.
+ *  \param value    Time value in seconds. */
 void mapper_timetag_set_int(mapper_timetag_t *tt, int value);
 
-/*! Set value of a mapper_timetag from a floating point value. */
+/*! Set value of a mapper_timetag from a floating point value.
+ *  \param tt       The timetag to set.
+ *  \param value    Time value in seconds. */
 void mapper_timetag_set_float(mapper_timetag_t *tt, float value);
 
-/*! Set value of a mapper_timetag from a double-precision floating point value. */
+/*! Set value of a mapper_timetag from a double-precision floating point value.
+ *  \param tt       The timetag to set.
+ *  \param value    Time value in seconds. */
 void mapper_timetag_set_double(mapper_timetag_t *tt, double value);
 
 /*! Copy value of a mapper_timetag. */
diff --git a/include/mapper/mapper_cpp.h b/include/mapper/mapper_cpp.h
new file mode 100644
index 0000000..fd368c1
--- /dev/null
+++ b/include/mapper/mapper_cpp.h
@@ -0,0 +1,1358 @@
+
+#ifndef _MAPPER_CPP_H_
+#define _MAPPER_CPP_H_
+
+#include <mapper/mapper.h>
+#include <mapper/mapper_types.h>
+#include <mapper/mapper_db.h>
+
+#include <functional>
+#include <memory>
+#include <list>
+#include <unordered_map>
+#include <string>
+#include <sstream>
+#include <initializer_list>
+#include <vector>
+#include <iterator>
+
+//#include <lo/lo.h>
+//#include <lo/lo_cpp.h>
+
+/* TODO:
+ *      signal update handlers
+ *      instance event handlers
+ *      monitor db handlers
+ *      device link & connection handlers
+ *      LinkProps: set scopes
+ *      Link and COnnection props: set arbitrary properties
+ */
+
+//signal_update_handler(Signal sig, instance_id, value, count, TimeTag)
+//- optional: instance_id, timetag, count
+//
+//possible forms:
+//(Signal sig, void *value)
+//(Signal sig, void *value, TimeTag tt)
+//(Signal sig, int instance_id, void *value)
+//(Signal sig, int instance_id, void *value, TimeTag tt)
+//(Signal sig, void *value, int count)
+//(Signal sig, void *value, int count, TimeTag tt)
+//(Signal sig, int instance_id, void *value, int count)
+//(Signal sig, int instance_id, void *value, int count, TimeTag tt)
+
+
+namespace mapper {
+
+    class Property;
+    class AbstractObjectProps;
+    class Db;
+
+    // Helper classes to allow polymorphism on "const char *",
+    // "std::string", and "int".
+    class string_type {
+    public:
+        string_type(const char *s=0) { _s = s; }
+        string_type(const std::string &s) { _s = s.c_str(); }
+        operator const char*() const { return _s; }
+        const char *_s;
+    };
+
+    class Admin
+    {
+    public:
+        Admin(const string_type &iface=0, const string_type &group=0, int port=0)
+            { admin = mapper_admin_new(iface, group, port); }
+        ~Admin()
+            { if (admin) mapper_admin_free(admin); }
+        operator mapper_admin() const
+            { return admin; }
+        std::string libversion()
+            { return std::string(mapper_admin_libversion(admin)); }
+    private:
+        mapper_admin admin;
+    };
+
+    class Timetag
+    {
+    public:
+        Timetag(mapper_timetag_t tt)
+            { timetag.sec = tt.sec; timetag.frac = tt.frac; }
+        Timetag(int seconds)
+            { mapper_timetag_set_int(&timetag, seconds); }
+        Timetag(float seconds)
+            { mapper_timetag_set_float(&timetag, seconds); }
+        Timetag(double seconds)
+            { mapper_timetag_set_double(&timetag, seconds); }
+        operator mapper_timetag_t*()
+            { return &timetag; }
+    private:
+        mapper_timetag_t timetag;
+    };
+
+    class AbstractProps
+    {
+    protected:
+        friend class Property;
+        virtual void set(mapper::Property *p) = 0;
+    public:
+        virtual void set(mapper::Property p) = 0;
+        virtual void remove(const string_type &name) = 0;
+    };
+
+    class Property
+    {
+    public:
+        template <typename T>
+        Property(const string_type &_name, T _value)
+            { name = _name; _set(_value); parent = NULL; owned = 0; }
+        template <typename T>
+        Property(const string_type &_name, T& _value, int _length)
+            { name = _name; _set(_value, _length); parent = NULL; owned = 0; }
+        template <typename T>
+        Property(const string_type &_name, std::vector<T> _value)
+            { name = _name; _set(_value); parent = NULL; owned = 0; }
+        template <typename T>
+        Property(const string_type &_name, char _type, T& _value, int _length)
+            { name = _name; _set(_type, _value, _length); parent = NULL; owned = 0; }
+
+        ~Property()
+            { maybe_free(); }
+
+        template <typename T>
+        void set(T _value)
+            { maybe_free(); _set(_value); if (parent) parent->set(this); }
+        template <typename T>
+        void set(T& _value, int _length)
+            { maybe_free(); _set(_value, _length); if (parent) parent->set(this); }
+        template <typename T>
+        void set(std::vector<T> _value)
+            { maybe_free(); _set(_value); if (parent) parent->set(this); }
+
+        operator const void*() const
+            { return value; }
+        void print()
+        {
+            printf("%s: ", name ?: "unknown");
+            mapper_prop_pp(type, length, value);
+        }
+        const char *name;
+        char type;
+        int length;
+        const void *value;
+    protected:
+        friend class AbstractDeviceProps;
+        friend class AbstractSignalProps;
+        friend class Db;
+        Property(const string_type &_name, char _type, const void *_value,
+                 int _length, const AbstractObjectProps *_parent)
+        {
+            name = _name;
+            _set(_type, _value, _length);
+            parent = (AbstractProps*)_parent;
+            owned = 0;
+        }
+        Property()
+            { name = 0; type = 0; length = 0; owned = 0; }
+    private:
+        union {
+            double d;
+            float f;
+            int i;
+            char c;
+        };
+        int owned;
+
+        void maybe_free()
+            { if (owned && value) free((void*)value); owned = 0; }
+        void _set(int _value)
+            { i = _value; length = 1; type = 'i'; value = &i; }
+        void _set(float _value)
+            { f = _value; length = 1; type = 'f'; value = &f; }
+        void _set(double _value)
+            { d = _value; length = 1; type = 'd'; value = &d; }
+        void _set(char _value)
+            { c = _value; length = 1; type = 'c'; value = &c; }
+        void _set(const string_type &_value)
+            { value = _value; length = 1; type = 's'; }
+        void _set(int _value[], int _length)
+            { value = _value; length = _length; type = 'i'; }
+        void _set(float _value[], int _length)
+            { value = _value; length = _length; type = 'f'; }
+        void _set(double _value[], int _length)
+            { value = _value; length = _length; type = 'd'; }
+        void _set(char _value[], int _length)
+            { value = _value; length = _length; type = 'c'; }
+        void _set(const char *_value[], int _length)
+            { value = _value; length = _length; type = 's'; }
+        template <size_t N>
+        void _set(std::array<int, N>& _value)
+        {
+            if (!_value.empty()) {
+                value = _value.data();
+                length = N;
+                type = 'i';
+            }
+            else
+                length = 0;
+        }
+        template <size_t N>
+        void _set(std::array<float, N>& _value)
+        {
+            if (!_value.empty()) {
+                value = _value.data();
+                length = N;
+                type = 'f';
+            }
+            else
+                length = 0;
+        }
+        template <size_t N>
+        void _set(std::array<double, N>& _value)
+        {
+            if (!_value.empty()) {
+                value = _value.data();
+                length = N;
+                type = 'd';
+            }
+            else
+                length = 0;
+        }
+        template <size_t N>
+        void _set(std::array<char, N>& _value)
+        {
+            if (!_value.empty()) {
+                value = _value.data();
+                length = N;
+                type = 'c';
+            }
+            else
+                length = 0;
+        }
+        template <size_t N>
+        void _set(std::array<const char*, N>& _value)
+        {
+            if (!_value.empty()) {
+                value = _value.data();
+                length = N;
+                type = 's';
+            }
+            else
+                length = 0;
+        }
+        template <size_t N>
+        void _set(std::array<std::string, N>& _values)
+        {
+            length = N;
+            type = 's';
+            if (length == 1) {
+                value = _values[0].c_str();
+            }
+            else if (length > 1) {
+                // need to copy string array
+                char **temp = (char**)malloc(sizeof(char*) * length);
+                for (i = 0; i < length; i++) {
+                    temp[i] = (char*)_values[i].c_str();
+                }
+                value = temp;
+                owned = 1;
+            }
+        }
+        void _set(std::string _values[], int _length)
+        {
+            length = _length;
+            type = 's';
+            if (length == 1) {
+                value = _values[0].c_str();
+            }
+            else if (length > 1) {
+                // need to copy string array
+                value = malloc(sizeof(char*) * length);
+                for (i = 0; i < length; i++) {
+                    ((char**)value)[i] = (char*)_values[i].c_str();
+                }
+                owned = 1;
+            }
+        }
+        void _set(std::vector<int> _value)
+            { value = _value.data(); length = _value.size(); type = 'i'; }
+        void _set(std::vector<float> _value)
+            { value = _value.data(); length = _value.size(); type = 'f'; }
+        void _set(std::vector<double> _value)
+            { value = _value.data(); length = _value.size(); type = 'd'; }
+        void _set(std::vector<char> _value)
+            { value = _value.data(); length = _value.size(); type = 'c'; }
+        void _set(std::vector<const char*>& _value)
+            { value = _value.data(); length = _value.size(); type = 's'; }
+        void _set(std::vector<std::string>& _value)
+        {
+            length = _value.size();
+            type = 's';
+            if (length == 1) {
+                value = _value[0].c_str();
+            }
+            else if (length > 1) {
+                // need to copy string array
+                value = malloc(sizeof(char*) * length);
+                for (i = 0; i < length; i++) {
+                    ((char**)value)[i] = (char*)_value[i].c_str();
+                }
+                owned = 1;
+            }
+        }
+        void _set(char _type, const void *_value, int _length)
+        {
+            type = _type;
+            value = _value;
+            length = _length;
+        }
+        AbstractProps *parent;
+    };
+
+    class AbstractObjectProps : public AbstractProps
+    {
+    protected:
+        virtual void set(mapper::Property *p) = 0;
+    public:
+        virtual void set(mapper::Property p) = 0;
+        virtual Property get(const string_type &name) const = 0;
+
+        Property operator [] (const string_type key)
+            { return get(key); }
+
+        template <typename T>
+        void set(const string_type &_name, T _value)
+            { set(Property(_name, _value)); }
+        template <typename T>
+        void set(const string_type &_name, T& _value, int _length)
+            { set(Property(_name, _value, _length)); }
+        template <typename T>
+        void set(const string_type &_name, std::vector<T> _value)
+            { set(Property(_name, _value)); }
+        template <typename T>
+        void set(const string_type &_name, char _type, T& _value, int _length)
+            { set(Property(_name, _type, _value, _length)); }
+    };
+
+    class AbstractSignalProps : public AbstractObjectProps
+    {
+    // Reuse class for signal and database
+    protected:
+        friend class Property;
+
+        AbstractSignalProps(mapper_signal sig)
+            { signal = sig; props = msig_properties(signal); found = 1; }
+        AbstractSignalProps(mapper_db_signal sig_db)
+            { signal = 0; props = sig_db; found = sig_db ? 1 : 0; }
+        void set(mapper::Property *p)
+        {
+            if (signal)
+                msig_set_property(signal, p->name, p->type,
+                                  p->type == 's' && p->length == 1
+                                  ? (void*)&p->value : (void*)p->value,
+                                  p->length);
+        }
+
+    private:
+        mapper_signal signal;
+        mapper_db_signal props;
+
+    public:
+        int found;
+        operator mapper_db_signal() const
+            { return props; }
+        using AbstractObjectProps::set;
+        void set(mapper::Property p)
+            { set(&p); }
+        void remove(const string_type &name)
+            { if (signal) msig_remove_property(signal, name); }
+        Property get(const string_type &name) const
+        {
+            char type;
+            const void *value;
+            int length;
+            if (!mapper_db_signal_property_lookup(props, name, &type,
+                                                  &value, &length))
+                return Property(name, type, value, length, this);
+            else
+                return Property();
+        }
+        Property get(int index) const
+        {
+            const char *name;
+            char type;
+            const void *value;
+            int length;
+            if (!mapper_db_signal_property_index(props, index, &name, &type,
+                                                 &value, &length))
+                return Property(name, type, value, length, this);
+            else
+                return Property();
+        }
+        class Iterator : public std::iterator<std::input_iterator_tag, int>
+        {
+        public:
+            Iterator(mapper_db_signal *s)
+                { sig = s; }
+            ~Iterator()
+                { mapper_db_signal_done(sig); }
+            operator mapper_db_signal*() const
+                { return sig; }
+            bool operator==(const Iterator& rhs)
+                { return (sig == rhs.sig); }
+            bool operator!=(const Iterator& rhs)
+                { return (sig != rhs.sig); }
+            Iterator& operator++()
+            {
+                if (sig != NULL)
+                    sig = mapper_db_signal_next(sig);
+                return (*this);
+            }
+            Iterator operator++(int)
+                { Iterator tmp(*this); operator++(); return tmp; }
+            AbstractSignalProps operator*()
+                { return AbstractSignalProps(*sig); }
+            Iterator begin()
+                { return Iterator(sig); }
+            Iterator end()
+                { return Iterator(0); }
+        private:
+            mapper_db_signal *sig;
+        };
+    };
+
+    class Signal
+    {
+    public:
+        Signal(mapper_signal sig)
+            { signal = sig; props = msig_properties(signal); }
+        ~Signal()
+            { ; }
+        operator mapper_signal() const
+            { return signal; }
+
+        // TODO: check if data type is correct in update!
+        void update(void *value, int count)
+            { msig_update(signal, value, count, MAPPER_NOW); }
+        void update(void *value, int count, Timetag tt)
+            { msig_update(signal, value, count, *tt); }
+        void update(int value)
+            { msig_update_int(signal, value); }
+        void update(float value)
+            { msig_update_float(signal, value); }
+        void update(double value)
+            { msig_update_double(signal, value); }
+        void update(int *value, int count=0)
+        {
+            if (props->type == 'i')
+                msig_update(signal, value, count, MAPPER_NOW);
+        }
+        void update(float *value, int count=0)
+        {
+            if (props->type == 'f')
+                msig_update(signal, value, count, MAPPER_NOW);
+        }
+        void update(double *value, int count=0)
+        {
+            if (props->type == 'd')
+                msig_update(signal, value, count, MAPPER_NOW);
+        }
+        void update(int *value, Timetag tt)
+        {
+            if (props->type == 'i')
+                msig_update(signal, value, 1, *tt);
+        }
+        void update(float *value, Timetag tt)
+        {
+            if (props->type == 'f')
+                msig_update(signal, value, 1, *tt);
+        }
+        void update(double *value, Timetag tt)
+        {
+            if (props->type == 'd')
+                msig_update(signal, value, 1, *tt);
+        }
+        void update(int *value, int count, Timetag tt)
+        {
+            if (props->type == 'i')
+                msig_update(signal, value, count, *tt);
+        }
+        void update(float *value, int count, Timetag tt)
+        {
+            if (props->type == 'f')
+                msig_update(signal, value, count, *tt);
+        }
+        void update(double *value, int count, Timetag tt)
+        {
+            if (props->type == 'd')
+                msig_update(signal, value, count, *tt);
+        }
+        void update(std::vector <int> value)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, MAPPER_NOW);
+        }
+        void update(std::vector <float> value)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, MAPPER_NOW);
+        }
+        void update(std::vector <double> value)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, MAPPER_NOW);
+        }
+        void update(std::vector <int> value, Timetag tt)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, *tt);
+        }
+        void update(std::vector <float> value, Timetag tt)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, *tt);
+        }
+        void update(std::vector <double> value, Timetag tt)
+        {
+            msig_update(signal, &value[0],
+                        value.size() / props->length, *tt);
+        }
+        void update_instance(int instance_id, void *value, int count)
+            { msig_update_instance(signal, instance_id, value, count, MAPPER_NOW); }
+        void update_instance(int instance_id, void *value, int count, Timetag tt)
+            { msig_update_instance(signal, instance_id, value, count, *tt); }
+        void update_instance(int instance_id, int value)
+            { msig_update_instance(signal, instance_id, &value, 1, MAPPER_NOW); }
+        void update_instance(int instance_id, float value)
+            { msig_update_instance(signal, instance_id, &value, 1, MAPPER_NOW); }
+        void update_instance(int instance_id, double value)
+            { msig_update_instance(signal, instance_id, &value, 1, MAPPER_NOW); }
+        void update_instance(int instance_id, int *value, int count=0)
+        {
+            if (props->type == 'i')
+                msig_update_instance(signal, instance_id, value, count, MAPPER_NOW);
+        }
+        void update_instance(int instance_id, float *value, int count=0)
+        {
+            if (props->type == 'f')
+                msig_update_instance(signal, instance_id, value, count, MAPPER_NOW);
+        }
+        void update_instance(int instance_id, double *value, int count=0)
+        {
+            if (props->type == 'd')
+                msig_update_instance(signal, instance_id, value, count, MAPPER_NOW);
+        }
+        void update_instance(int instance_id, int *value, Timetag tt)
+        {
+            if (props->type == 'i')
+                msig_update_instance(signal, instance_id, value, 1, *tt);
+        }
+        void update_instance(int instance_id, float *value, Timetag tt)
+        {
+            if (props->type == 'f')
+                msig_update_instance(signal, instance_id, value, 1, *tt);
+        }
+        void update_instance(int instance_id, double *value, Timetag tt)
+        {
+            if (props->type == 'd')
+                msig_update_instance(signal, instance_id, value, 1, *tt);
+        }
+        void update_instance(int instance_id, int *value, int count, Timetag tt)
+        {
+            if (props->type == 'i')
+                msig_update_instance(signal, instance_id, value, count, *tt);
+        }
+        void update_instance(int instance_id, float *value, int count, Timetag tt)
+        {
+            if (props->type == 'f')
+                msig_update_instance(signal, instance_id, value, count, *tt);
+        }
+        void update_instance(int instance_id, double *value, int count, Timetag tt)
+        {
+            if (props->type == 'd')
+                msig_update_instance(signal, instance_id, value, count, *tt);
+        }
+        void *value() const
+            { return msig_value(signal, 0); }
+        void *value(Timetag tt) const
+            { return msig_value(signal, tt); }
+        void *instance_value(int instance_id) const
+            { return msig_instance_value(signal, instance_id, 0); }
+        void *instance_value(int instance_id, Timetag tt) const
+            { return msig_instance_value(signal, instance_id, tt); }
+        int query_remotes() const
+            { return msig_query_remotes(signal, MAPPER_NOW); }
+        int query_remotes(Timetag tt) const
+            { return msig_query_remotes(signal, *tt); }
+        void reserve_instances(int num)
+            { msig_reserve_instances(signal, num, 0, 0); }
+        void reserve_instances(int num, int *instance_ids, void **user_data)
+            { msig_reserve_instances(signal, num, instance_ids, user_data); }
+        void release_instance(int instance_id)
+            { msig_release_instance(signal, instance_id, MAPPER_NOW); }
+        void release_instance(int instance_id, Timetag tt)
+            { msig_release_instance(signal, instance_id, *tt); }
+        void remove_instance(int instance_id)
+            { msig_remove_instance(signal, instance_id); }
+        int oldest_active_instance(int *instance_id)
+            { return msig_get_oldest_active_instance(signal, instance_id); }
+        int newest_active_instance(int *instance_id)
+            { return msig_get_newest_active_instance(signal, instance_id); }
+        int num_active_instances() const
+            { return msig_num_active_instances(signal); }
+        int num_reserved_instances() const
+            { return msig_num_reserved_instances(signal); }
+        int active_instance_id(int index) const
+            { return msig_active_instance_id(signal, index); }
+        void set_instance_allocation_mode(mapper_instance_allocation_type mode)
+            { msig_set_instance_allocation_mode(signal, mode); }
+        mapper_instance_allocation_type get_instance_allocation_mode() const
+            { return msig_get_instance_allocation_mode(signal); }
+        void set_instance_event_callback(mapper_signal_instance_event_handler h,
+                                         int flags, void *user_data)
+            { msig_set_instance_event_callback(signal, h, flags, user_data); }
+        void set_instance_data(int instance_id, void *user_data)
+            { msig_set_instance_data(signal, instance_id, user_data); }
+        void *instance_data(int instance_id) const
+            { return msig_get_instance_data(signal, instance_id); }
+        void set_callback(mapper_signal_update_handler *handler, void *user_data)
+            { msig_set_callback(signal, handler, user_data); }
+        int num_connections() const
+            { return msig_num_connections(signal); }
+        std::string full_name() const
+        {
+            char str[64];
+            msig_full_name(signal, str, 64);
+            return std::string(str);
+        }
+        void set_minimum(void *value)
+            { msig_set_minimum(signal, value); }
+        void set_maximum(void *value)
+            { msig_set_maximum(signal, value); }
+        void set_rate(int rate)
+            { msig_set_rate(signal, rate); }
+        class Props : public AbstractSignalProps
+        {
+        public:
+            Props(mapper_signal s) : AbstractSignalProps(s) {}
+        };
+        Props properties() const
+            { return Props(signal); }
+        Property property(const string_type name)
+            { return Props(signal).get(name); }
+        class Iterator : public std::iterator<std::input_iterator_tag, int>
+        {
+        public:
+            Iterator(mapper_signal *s, int n)
+                { signals = s; size = n; }
+            ~Iterator() {}
+            bool operator==(const Iterator& rhs)
+                { return (signals == rhs.signals && size == rhs.size); }
+            bool operator!=(const Iterator& rhs)
+                { return (signals != rhs.signals || size != rhs.size); }
+            Iterator& operator++()
+                { size++; return (*this); }
+            Iterator operator++(int)
+                { Iterator tmp(*this); size++; return tmp; }
+            Signal operator*()
+                { return Signal(signals[size]); }
+            Iterator begin()
+                { return Iterator(signals, 0); }
+            Iterator end()
+                { return Iterator(signals, size); }
+        private:
+            mapper_signal *signals;
+            int size;
+        };
+    private:
+        mapper_signal signal;
+        mapper_db_signal props;
+    };
+
+    class AbstractDeviceProps : public AbstractObjectProps
+    {
+    // Reuse same class for device and database
+    protected:
+        friend class Property;
+
+        AbstractDeviceProps(mapper_device dev) : AbstractObjectProps()
+            { device = dev; props = mdev_properties(device); found = 1; }
+        AbstractDeviceProps(mapper_db_device dev_db)
+            { device = 0; props = dev_db; found = dev_db ? 1 : 0; }
+        void set(mapper::Property* p)
+        {
+            if (device)
+                mdev_set_property(device, p->name, p->type,
+                                  p->type == 's' && p->length == 1 ?
+                                  (void*)&p->value : (void*)p->value,
+                                  p->length);
+        }
+
+    private:
+        mapper_device device;
+        mapper_db_device props;
+
+    public:
+        int found;
+        operator mapper_db_device() const
+            { return props; }
+        using AbstractObjectProps::set;
+        void set(mapper::Property p)
+            { set(&p); }
+        void remove(const string_type &name)
+            { if (device) mdev_remove_property(device, name); }
+        Property get(const string_type &name) const
+        {
+            char type;
+            const void *value;
+            int length;
+            if (!mapper_db_device_property_lookup(props, name, &type,
+                                                  &value, &length))
+                return Property(name, type, value, length, this);
+            else
+                return Property();
+        }
+        Property get(int index) const
+        {
+            const char *name;
+            char type;
+            const void *value;
+            int length;
+            if (!mapper_db_device_property_index(props, index, &name, &type,
+                                                 &value, &length))
+                return Property(name, type, value, length, this);
+            else
+                return Property();
+        }
+        class Iterator : public std::iterator<std::input_iterator_tag, int>
+        {
+        public:
+            Iterator(mapper_db_device *d)
+                { dev = d; }
+            ~Iterator()
+                { mapper_db_device_done(dev); }
+            operator mapper_db_device*() const
+                { return dev; }
+            bool operator==(const Iterator& rhs)
+                { return (dev == rhs.dev); }
+            bool operator!=(const Iterator& rhs)
+                { return (dev != rhs.dev); }
+            Iterator& operator++()
+                {
+                    if (dev != NULL)
+                        dev = mapper_db_device_next(dev);
+                    return (*this);
+                }
+            Iterator operator++(int)
+                { Iterator tmp(*this); operator++(); return tmp; }
+            AbstractDeviceProps operator*()
+                { return AbstractDeviceProps(*dev); }
+            Iterator begin()
+                { return Iterator(dev); }
+            Iterator end()
+                { return Iterator(0); }
+        private:
+            mapper_db_device *dev;
+        };
+    };
+
+    class Device
+    {
+    public:
+        Device(const string_type &name_prefix, int port, Admin admin)
+            { device = mdev_new(name_prefix, port, admin); }
+        Device(const string_type &name_prefix)
+            { device = mdev_new(name_prefix, 0, 0); }
+        ~Device()
+            { if (device) mdev_free(device); }
+        Signal add_input(const string_type &name, int length, char type,
+                         const string_type &unit, void *minimum,
+                         void *maximum, mapper_signal_update_handler handler,
+                         void *user_data)
+        {
+            return Signal(mdev_add_input(device, name, length, type, unit,
+                                         minimum, maximum, handler, user_data));
+        }
+        Signal add_output(const string_type &name, int length, char type,
+                          const string_type &unit, void *minimum, void *maximum)
+        {
+            return Signal(mdev_add_output(device, name, length, type, unit,
+                                          minimum, maximum));
+        }
+        void remove_input(Signal input)
+            { if (input) mdev_remove_input(device, input); }
+        void remove_input(const string_type &name)
+        {
+            if (!name)
+                return;
+            mapper_signal input = mdev_get_input_by_name(device, name, 0);
+            mdev_remove_input(device, input);
+        }
+        void remove_output(Signal output)
+            { if (output) mdev_remove_output(device, output); }
+        void remove_output(const string_type &name)
+        {
+            if (!name)
+                return;
+            mapper_signal output = mdev_get_output_by_name(device, name, 0);
+            mdev_remove_output(device, output);
+        }
+        int num_inputs() const
+            { return mdev_num_inputs(device); }
+        int num_outputs() const
+            { return mdev_num_outputs(device); }
+        int num_links_in() const
+            { return mdev_num_links_in(device); }
+        int num_links_out() const
+            { return mdev_num_links_out(device); }
+        int num_connections_in() const
+            { return mdev_num_connections_in(device); }
+        int num_connections_out() const
+            { return mdev_num_connections_out(device); }
+        Signal::Iterator inputs() const
+        {
+            return Signal::Iterator(mdev_get_inputs(device),
+                                    mdev_num_inputs(device));
+        }
+        Signal inputs(const string_type &name, int* index=0) const
+            { return Signal(mdev_get_input_by_name(device, name, index)); }
+        Signal inputs(int index) const
+            { return Signal(mdev_get_input_by_index(device, index)); }
+        Signal::Iterator outputs() const
+        {
+            return Signal::Iterator(mdev_get_outputs(device),
+                                    mdev_num_outputs(device));
+        }
+        Signal outputs(const string_type &name, int *index=0) const
+            { return Signal(mdev_get_output_by_name(device, name, index)); }
+        Signal outputs(int index) const
+            { return Signal(mdev_get_output_by_index(device, index)); }
+        class Props : public AbstractDeviceProps
+        {
+        public:
+            Props(mapper_device d) : AbstractDeviceProps(d) {}
+        };
+        Props properties() const
+            { return Props(device); }
+        Property property(const string_type name)
+            { return Props(device).get(name); }
+        int poll(int block_ms=0) const
+            { return mdev_poll(device, block_ms); }
+        int num_fds() const
+            { return mdev_num_fds(device); }
+        int fds(int *fds, int num) const
+            { return mdev_get_fds(device, fds, num); }
+        void service_fd(int fd)
+            { mdev_service_fd(device, fd); }
+        bool ready() const
+            { return mdev_ready(device); }
+        std::string name() const
+            { return std::string(mdev_name(device)); }
+        int id() const
+            { return mdev_id(device); }
+        int port() const
+            { return mdev_port(device); }
+        const struct in_addr *ip4() const
+            { return mdev_ip4(device); }
+        std::string interface() const
+            { return mdev_interface(device); }
+        int ordinal() const
+            { return mdev_ordinal(device); }
+        void start_queue(Timetag tt) const
+            { mdev_start_queue(device, *tt); }
+        void send_queue(Timetag tt) const
+            { mdev_send_queue(device, *tt); }
+//        lo::Server get_lo_server()
+//            { return lo::Server(mdev_get_lo_server(device)); }
+        void set_link_callback(mapper_device_link_handler handler, void *user_data)
+            { mdev_set_link_callback(device, handler, user_data); }
+        void set_connection_callback(mapper_device_connection_handler handler,
+                                     void *user_data)
+            { mdev_set_connection_callback(device, handler, user_data); }
+        Timetag now()
+        {
+            mapper_timetag_t tt;
+            mdev_now(device, &tt);
+            return Timetag(tt);
+        }
+    private:
+        mapper_device device;
+    };
+
+    class Db
+    {
+    public:
+        Db(mapper_monitor mon)
+        {
+            monitor = mon;
+            db = mapper_monitor_get_db(mon);
+        }
+        ~Db()
+        {}
+        void flush()
+        {
+            mapper_monitor_flush_db(monitor,
+                                    mapper_monitor_get_timeout(monitor), 0);
+        }
+        void flush(int timeout_sec, int quiet=0)
+            { mapper_monitor_flush_db(monitor, timeout_sec, quiet); }
+
+        // db_devices
+        void add_device_callback(mapper_db_device_handler *handler,
+                                 void *user_data)
+            { mapper_db_add_device_callback(db, handler, user_data); }
+        void remove_device_callback(mapper_db_device_handler *handler,
+                                    void *user_data)
+            { mapper_db_remove_device_callback(db, handler, user_data); }
+
+        class Device : public AbstractDeviceProps
+        {
+        public:
+            Device(mapper_db_device d) : AbstractDeviceProps(d) {}
+        };
+
+        Device device(const string_type &name) const
+            { return Device(mapper_db_get_device_by_name(db, name)); }
+        Device device(uint32_t hash) const
+            { return Device(mapper_db_get_device_by_name_hash(db, hash)); }
+        Device::Iterator devices() const
+            { return Device::Iterator(mapper_db_get_all_devices(db)); }
+        Device::Iterator devices(const string_type &pattern) const
+        {
+            return Device::Iterator(
+                mapper_db_match_devices_by_name(db, pattern));
+        }
+
+        // db_signals
+        void add_signal_callback(mapper_db_signal_handler *handler,
+                                 void *user_data)
+            { mapper_db_add_signal_callback(db, handler, user_data); }
+        void remove_signal_callback(mapper_db_signal_handler *handler,
+                                    void *user_data)
+            { mapper_db_remove_signal_callback(db, handler, user_data); }
+
+        class Signal : public AbstractSignalProps
+        {
+        public:
+            Signal(mapper_db_signal s) : AbstractSignalProps(s) {}
+        };
+
+        Signal input(const string_type &device_name,
+                     const string_type &signal_name)
+        {
+            return Signal(
+                mapper_db_get_input_by_device_and_signal_names(db, device_name,
+                                                               signal_name));
+        }
+        Signal output(const string_type &device_name,
+                      const string_type &signal_name)
+        {
+            return Signal(
+                mapper_db_get_output_by_device_and_signal_names(db, device_name,
+                                                                signal_name));
+        }
+        Signal::Iterator inputs() const
+            { return Signal::Iterator(mapper_db_get_all_inputs(db)); }
+        Signal::Iterator inputs(const string_type device_name) const
+        {
+            return Signal::Iterator(
+                mapper_db_get_inputs_by_device_name(db, device_name));
+        }
+        Signal::Iterator match_inputs(const string_type device_name,
+                                      const string_type pattern) const
+        {
+            return Signal::Iterator(
+                 mapper_db_match_inputs_by_device_name(db, device_name, pattern));
+        }
+        Signal::Iterator outputs() const
+            { return Signal::Iterator(mapper_db_get_all_outputs(db)); }
+        Signal::Iterator outputs(const string_type device_name) const
+        {
+            return Signal::Iterator(
+                 mapper_db_get_outputs_by_device_name(db, device_name));
+        }
+        Signal::Iterator match_outputs(const string_type device_name,
+                                       const string_type pattern) const
+        {
+            return Signal::Iterator(
+                 mapper_db_match_outputs_by_device_name(db, device_name, pattern));
+        }
+
+        // db_links
+        void add_link_callback(mapper_db_link_handler *handler,
+                               void *user_data)
+            { mapper_db_add_link_callback(db, handler, user_data); }
+        void remove_link_callback(mapper_db_link_handler *handler,
+                                  void *user_data)
+            { mapper_db_remove_link_callback(db, handler, user_data); }
+        class Link : AbstractObjectProps
+        {
+        public:
+            int found;
+            Link(mapper_db_link link)
+                { props = link; found = link ? 1 : 0; owned = 0; }
+            Link()
+            {
+                props = (mapper_db_link)calloc(1, sizeof(mapper_db_link_t));
+                owned = 1;
+            }
+            ~Link()
+                { if (owned && props) free(props); }
+            operator mapper_db_link() const
+                { return props; }
+            void set(Property p) {}
+            void remove(const string_type &name) {}
+            Property get(const string_type &name) const
+            {
+                char type;
+                const void *value;
+                int length;
+                if (!mapper_db_link_property_lookup(props, name, &type,
+                                                    &value, &length))
+                    return Property(name, type, value, length);
+                else
+                    return Property();
+            }
+            Property get(int index) const
+            {
+                const char *name;
+                char type;
+                const void *value;
+                int length;
+                if (!mapper_db_link_property_index(props, index, &name, &type,
+                                                   &value, &length))
+                    return Property(name, type, value, length);
+                else
+                    return Property();
+            }
+            class Iterator : public std::iterator<std::input_iterator_tag, int>
+            {
+            public:
+                Iterator(mapper_db_link *l)
+                    { link = l; }
+                ~Iterator()
+                    { mapper_db_link_done(link); }
+                bool operator==(const Iterator& rhs)
+                    { return (link == rhs.link); }
+                bool operator!=(const Iterator& rhs)
+                    { return (link != rhs.link); }
+                Iterator& operator++()
+                {
+                    if (link != NULL)
+                        link = mapper_db_link_next(link);
+                    return (*this);
+                }
+                Iterator operator++(int)
+                    { Iterator tmp(*this); operator++(); return tmp; }
+                Link operator*()
+                    { return Link(*link); }
+                Iterator begin()
+                    { return Iterator(link); }
+                Iterator end()
+                    { return Iterator(0); }
+            private:
+                mapper_db_link *link;
+            };
+        protected:
+            void set(Property *p) {}
+        private:
+            mapper_db_link props;
+            int owned;
+        };
+        Link::Iterator links() const
+            { return Link::Iterator(mapper_db_get_all_links(db)); }
+        Link::Iterator links(const string_type &device_name) const
+        {
+            return Link::Iterator(
+                mapper_db_get_links_by_device_name(db, device_name));
+        }
+        Link::Iterator links_by_src(const string_type &device_name) const
+        {
+            return Link::Iterator(
+                mapper_db_get_links_by_src_device_name(db, device_name));
+        }
+        Link::Iterator links_by_dest(const string_type &device_name) const
+        {
+            return Link::Iterator(
+                mapper_db_get_links_by_dest_device_name(db, device_name));
+        }
+        Link link(const string_type &source_device,
+                  const string_type &dest_device)
+        {
+            return Link(
+                mapper_db_get_link_by_src_dest_names(db, source_device,
+                                                     dest_device));
+        }
+        Link::Iterator links(Device::Iterator src_list,
+                             Device::Iterator dest_list) const
+        {
+            // TODO: check that this works!
+            return Link::Iterator(
+                mapper_db_get_links_by_src_dest_devices(db,
+                    (mapper_db_device*)(src_list),
+                    (mapper_db_device*)(dest_list)));
+        }
+
+        // db connections
+        void add_connection_callback(mapper_db_connection_handler *handler,
+                                     void *user_data)
+            { mapper_db_add_connection_callback(db, handler, user_data); }
+        void remove_connection_callback(mapper_db_connection_handler *handler,
+                                        void *user_data)
+            { mapper_db_remove_connection_callback(db, handler, user_data); }
+        class Connection : AbstractObjectProps
+        {
+        public:
+            int found;
+            int flags;
+            char src_type;
+            char dest_type;
+            int src_length;
+            int dest_length;
+            void *src_min;
+            void *src_max;
+            void *dest_min;
+            void *dest_max;
+
+            Connection(mapper_db_connection connection)
+                { props = connection; found = connection ? 1 : 0; owned = 0; }
+            Connection()
+            {
+                props = (mapper_db_connection)calloc(1, sizeof(mapper_db_connection_t));
+                flags = 0;
+                owned = 1;
+            }
+            ~Connection()
+                { if (owned && props) free(props); }
+            operator mapper_db_connection() const
+            {
+                return props;
+            }
+            void set(Property p) {}
+            void remove(const string_type &name) {}
+            void set_mode(mapper_mode_type mode)
+                { props->mode = mode; flags |= CONNECTION_MODE; }
+            void set_bound_min(mapper_boundary_action bound_min)
+                { props->bound_min = bound_min; flags |= CONNECTION_BOUND_MIN; }
+            void set_bound_max(mapper_boundary_action bound_max)
+                { props->bound_max = bound_max; flags |= CONNECTION_BOUND_MAX; }
+            void set_expression(const string_type &expression)
+            {
+                props->expression = (char*)(const char*)expression;
+                flags |= CONNECTION_EXPRESSION;
+            }
+            void set_src_min(const Property &value)
+            {
+                props->src_min = (void*)(const void*)value;
+                props->src_type = value.type;
+                props->src_length = value.length;
+                flags |= (CONNECTION_RANGE_SRC_MIN | CONNECTION_SRC_TYPE | CONNECTION_SRC_LENGTH);
+            }
+            void set_src_max(Property &value)
+            {
+                props->src_max = (void*)(const void*)value;
+                props->src_type = value.type;
+                props->src_length = value.length;
+                flags |= (CONNECTION_RANGE_SRC_MAX | CONNECTION_SRC_TYPE | CONNECTION_SRC_LENGTH);
+            }
+            void set_dest_min(Property &value)
+            {
+                props->dest_min = (void*)(const void*)value;
+                props->dest_type = value.type;
+                props->dest_length = value.length;
+                flags |= (CONNECTION_RANGE_DEST_MIN | CONNECTION_DEST_TYPE | CONNECTION_DEST_LENGTH);
+            }
+            void set_dest_max(Property &value)
+            {
+                props->dest_min = (void*)(const void*)value;
+                props->dest_type = value.type;
+                props->dest_length = value.length;
+                flags |= (CONNECTION_RANGE_DEST_MAX | CONNECTION_DEST_TYPE | CONNECTION_DEST_LENGTH);
+            }
+            Property get(const string_type &name) const
+            {
+                char type;
+                const void *value;
+                int length;
+                if (!mapper_db_connection_property_lookup(props, name, &type,
+                                                          &value, &length))
+                    return Property(name, type, value, length);
+                else
+                    return Property();
+            }
+            Property get(int index) const
+            {
+                const char *name;
+                char type;
+                const void *value;
+                int length;
+                if (!mapper_db_connection_property_index(props, index, &name,
+                                                         &type, &value, &length))
+                    return Property(name, type, value, length);
+                else
+                    return Property();
+            }
+            class Iterator : public std::iterator<std::input_iterator_tag, int>
+            {
+            public:
+                Iterator(mapper_db_connection *c)
+                    { con = c; }
+                ~Iterator()
+                    { mapper_db_connection_done(con); }
+                bool operator==(const Iterator& rhs)
+                    { return (con == rhs.con); }
+                bool operator!=(const Iterator& rhs)
+                    { return (con != rhs.con); }
+                Iterator& operator++()
+                {
+                    if (con != NULL)
+                        con = mapper_db_connection_next(con);
+                    return (*this);
+                }
+                Iterator operator++(int)
+                    { Iterator tmp(*this); operator++(); return tmp; }
+                Connection operator*()
+                    { return Connection(*con); }
+                Iterator begin()
+                    { return Iterator(con); }
+                Iterator end()
+                    { return Iterator(0); }
+            private:
+                mapper_db_connection *con;
+            };
+        protected:
+            void set(mapper::Property *p) {}
+        private:
+            mapper_db_connection props;
+            int owned;
+        };
+        Connection::Iterator connections() const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_all_connections(db));
+        }
+        Connection::Iterator connections(const string_type &src_device,
+                                         const string_type &src_signal,
+                                         const string_type &dest_device,
+                                         const string_type &dest_signal) const
+        {
+            return Connection::Iterator(
+                mapper_db_get_connections_by_device_and_signal_names(db,
+                     src_device, src_signal, dest_device, dest_signal));
+        }
+        Connection::Iterator connections(const string_type &device_name) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_device_name(db, device_name));
+        }
+        Connection::Iterator connections_by_src(const string_type &signal_name) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_src_signal_name(db, signal_name));
+        }
+        Connection::Iterator connections_by_src(const string_type &device_name,
+                                                const string_type &signal_name) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_src_device_and_signal_names(db,
+                      device_name, signal_name));
+        }
+        Connection::Iterator connections_by_dest(const string_type &signal_name) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_dest_signal_name(db, signal_name));
+        }
+        Connection::Iterator connections_by_dest(const string_type &device_name,
+                                                 const string_type &signal_name) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_dest_device_and_signal_names(db,
+                      device_name, signal_name));
+        }
+        Connection connection_by_signals(const string_type &source_name,
+                                         const string_type &dest_name) const
+        {
+            return Connection(
+                mapper_db_get_connection_by_signal_full_names(db, source_name,
+                                                              dest_name));
+        }
+        Connection::Iterator connections_by_devices(const string_type &source_name,
+                                                    const string_type &dest_name) const
+        {
+            return Connection::Iterator(
+                mapper_db_get_connections_by_src_dest_device_names(db,
+                                                                   source_name,
+                                                                   dest_name));
+        }
+        Connection::Iterator connections(Signal::Iterator src_list,
+                                         Signal::Iterator dest_list) const
+        {
+            return Connection::Iterator(
+                 mapper_db_get_connections_by_signal_queries(db,
+                     (mapper_db_signal*)(src_list),
+                     (mapper_db_signal*)(dest_list)));
+        }
+    private:
+        mapper_db db;
+        mapper_monitor monitor;
+    };
+
+    class Monitor
+    {
+    public:
+        Monitor()
+            { monitor = mapper_monitor_new(0, 0); }
+        Monitor(Admin admin, int autosubscribe_flags=0)
+            { monitor = mapper_monitor_new(admin, autosubscribe_flags); }
+        Monitor(int autosubscribe_flags)
+            { monitor = mapper_monitor_new(0, autosubscribe_flags); }
+        ~Monitor()
+            { if (monitor) mapper_monitor_free(monitor); }
+        int poll(int block_ms=0)
+            { return mapper_monitor_poll(monitor, block_ms); }
+        Db db() const
+            { return Db(monitor); }
+        void request_devices() const
+            { mapper_monitor_request_devices(monitor); }
+        void subscribe(const string_type &device_name, int flags, int timeout)
+            { mapper_monitor_subscribe(monitor, device_name, flags, timeout); }
+        void unsubscribe(const string_type &device_name)
+            { mapper_monitor_unsubscribe(monitor, device_name); }
+        void autosubscribe(int flags) const
+            { mapper_monitor_autosubscribe(monitor, flags); }
+        void link(const string_type &source_device, const string_type &dest_device)
+            { mapper_monitor_link(monitor, source_device, dest_device, 0, 0); }
+//        void link(const string_type &source_device, const string_type &dest_device,
+//             const LinkProps &properties)
+//        {
+//            mapper_monitor_link(monitor, source_device, dest_device,
+//                                mapper_db_link(properties), properties.flags);
+//        }
+        void unlink(const string_type &source_device, const string_type &dest_device)
+            { mapper_monitor_unlink(monitor, source_device, dest_device); }
+        void connect(const string_type &source_signal, const string_type &dest_signal)
+            { mapper_monitor_connect(monitor, source_signal, dest_signal, 0, 0); }
+        void connect(const string_type &source_signal,
+                     const string_type &dest_signal,
+                     const Db::Connection &properties) const
+        {
+            mapper_monitor_connect(monitor, source_signal, dest_signal,
+                                   mapper_db_connection(properties),
+                                   properties.flags);
+        }
+        void connection_modify(const string_type &source_signal,
+                               const string_type &dest_signal,
+                               Db::Connection &properties) const
+        {
+            mapper_monitor_connection_modify(monitor, source_signal, dest_signal,
+                                             mapper_db_connection(properties),
+                                             properties.flags);
+        }
+        void disconnect(const string_type &source_signal, const string_type &dest_signal)
+            { mapper_monitor_disconnect(monitor, source_signal, dest_signal); }
+    private:
+        mapper_monitor monitor;
+    };
+};
+
+#endif // _MAPPER_CPP_H_
diff --git a/include/mapper/mapper_db.h b/include/mapper/mapper_db.h
index 32becb4..e281238 100644
--- a/include/mapper/mapper_db.h
+++ b/include/mapper/mapper_db.h
@@ -23,23 +23,24 @@ typedef lo_timetag mapper_timetag_t;
 /*! A record that keeps information about a device on the network.
  *  @ingroup devicedb */
 typedef struct _mapper_db_device {
-    char *identifier;       /*!< The identifier (prefix) for
-                             *   this device. */
-    char *name;             /*!< The full name for this
-                             *   device, or zero. */
+    char *identifier;           /*!< The identifier (prefix) for
+                                 *   this device. */
+    char *name;                 /*!< The full name for this
+                                 *   device, or zero. */
     int ordinal;
-    uint32_t name_hash;     /*!< CRC-32 hash of full device name
-                             *   in the form <name>.<ordinal> */
-    char *host;             //!< Device network host name.
-    int port;               //!< Device network port.
-    int n_inputs;           //!< Number of associated input signals.
-    int n_outputs;          //!< Number of associated output signals.
-    int n_links_in;         //!< Number of associated incoming links.
-    int n_links_out;        //!< Number of associated outgoing links.
-    int n_connections_in;   //!< Number of associated incoming connections.
-    int n_connections_out;  //!< Number of associated outgoing connections.
-    int version;            //!< Reported device state version.
-    void* user_data;        //!< User modifiable data.
+    uint32_t name_hash;         /*!< CRC-32 hash of full device name
+                                 *   in the form <name>.<ordinal> */
+    char *host;                 //!< Device network host name.
+    int port;                   //!< Device network port.
+    int num_inputs;             //!< Number of associated input signals.
+    int num_outputs;            //!< Number of associated output signals.
+    int num_links_in;           //!< Number of associated incoming links.
+    int num_links_out;          //!< Number of associated outgoing links.
+    int num_connections_in;     //!< Number of associated incoming connections.
+    int num_connections_out;    //!< Number of associated outgoing connections.
+    int version;                //!< Reported device state version.
+    char *lib_version;          //!< libmapper version of device.
+    void* user_data;            //!< User modifiable data.
     mapper_timetag_t timetag;
 
     mapper_timetag_t synced; //!< Timestamp of last sync.
@@ -183,8 +184,6 @@ typedef struct _mapper_signal_history
 
     /*! Timetag for each sample of stored history. */
     mapper_timetag_t *timetag;
-
-    struct _mapper_signal_history *next;
 } mapper_signal_history_t;
 
 /*! A record that describes properties of a signal.
diff --git a/jni/Makefile.am b/jni/Makefile.am
index 4f4884b..18ed188 100644
--- a/jni/Makefile.am
+++ b/jni/Makefile.am
@@ -6,13 +6,26 @@ libmapperjni_ at MAJOR_VERSION@_la_LDFLAGS = -export-dynamic -version-info @SO_VERS
 libmapperjni_ at MAJOR_VERSION@_la_LIBADD = \
 	$(top_builddir)/src/libmapper- at MAJOR_VERSION@.la $(liblo_LIBS)
 
-JHEADERS = Mapper_Device.h Mapper_Device_Signal.h Mapper_Db_Signal.h
+JHEADERS = Mapper_Device.h Mapper_Device_Signal.h Mapper_Db_Signal.h        \
+           Mapper_Db_Device.h Mapper_Db_Link.h Mapper_Db_Connection.h       \
+           Mapper_Monitor.h Mapper_Monitor_Db.h Mapper_Db_DeviceIterator.h  \
+           Mapper_Db_SignalIterator.h Mapper_Db_LinkIterator.h              \
+           Mapper_Db_ConnectionIterator.h
 
 JFLAGS = -source 1.5 -target 1.5
-JCLASSES = Mapper/Db/Signal.class Mapper/InputListener.class	\
-           Mapper/PropertyValue.class Mapper/Device.class		\
-           Mapper/NativeLib.class Mapper/TimeTag.class			\
-           Mapper/InstanceEventListener.class
+JCLASSES = Mapper/NativeLib.class Mapper/Device.class                       \
+           Mapper/PropertyValue.class Mapper/TimeTag.class                  \
+           Mapper/InputListener.class Mapper/InstanceEventListener.class    \
+           Mapper/Monitor.class Mapper/Db/Device.class                      \
+           Mapper/Db/Signal.class Mapper/Db/Link.class                      \
+           Mapper/Db/Connection.class Mapper/Db/DeviceListener.class        \
+           Mapper/Db/SignalListener.class Mapper/Db/LinkListener.class      \
+           Mapper/Db/ConnectionListener.class                               \
+           Mapper/Db/DeviceCollection.class Mapper/Db/DeviceIterator.class  \
+           Mapper/Db/SignalCollection.class Mapper/Db/SignalIterator.class  \
+           Mapper/Db/LinkCollection.class Mapper/Db/LinkIterator.class      \
+           Mapper/Db/ConnectionCollection.class                             \
+           Mapper/Db/ConnectionIterator.class
 
 JCLASSESINTERNAL = Mapper/Device$$Signal.class Mapper/Device$$1.class	\
                    Mapper/PropertyValue$$PropertyException.class
@@ -20,11 +33,11 @@ JCLASSESINTERNAL = Mapper/Device$$Signal.class Mapper/Device$$1.class	\
 JJAVA = $(JCLASSES:%.class=%.java)
 
 BUILT_SOURCES = $(JHEADERS) test.class testquery.class testreverse.class \
-    libmapper- at MAJOR_VERSION@.jar
+    testspeed.class libmapper- at MAJOR_VERSION@.jar
 MOSTLYCLEANFILES = $(BUILT_SOURCES) $(JCLASSES) $(subst $$,\$$,$(JCLASSESINTERNAL)) \
 	$(subst $$,\$$,test$$1.class test$$2.class test$$3.class testquery$$1.class \
     testquery$$2.class testquery$$3.class testreverse$$1.class testreverse$$2.class \
-    testreverse$$3.class)
+    testreverse$$3.class testspeed$$1.class testspeed$$2.class testspeed$$3.class)
 
 Mapper_%.h: Mapper/%.class
 	$(JAVAH) $(subst /,.,$(<:%.class=%))
@@ -46,6 +59,9 @@ testquery.class: testquery.java $(JCLASSES)
 testreverse.class: testreverse.java $(JCLASSES)
 	$(JAVAC) $(JFLAGS) -sourcepath @top_srcdir@/jni -d . $<
 
+testspeed.class: testspeed.java $(JCLASSES)
+	$(JAVAC) $(JFLAGS) -sourcepath @top_srcdir@/jni -d . $<
+
 libmapper- at MAJOR_VERSION@.jar: $(JCLASSES) $(JCLASSESINTERNAL)
 	$(JAR) cvf $@ $(subst $$,\$$,$^)
 	if mkdir -v TestInstances/code; then \
@@ -54,4 +70,5 @@ libmapper- at MAJOR_VERSION@.jar: $(JCLASSES) $(JCLASSESINTERNAL)
 	  ln -v -s ../../$@; \
 	fi # For processing test sketch
 
-EXTRA_DIST = $(JJAVA) test.java testquery.java testreverse.java $(JHEADERS)
+EXTRA_DIST = $(JJAVA) test.java testquery.java testreverse.java testspeed.java \
+             $(JHEADERS)
diff --git a/jni/Mapper/Db/Connection.java b/jni/Mapper/Db/Connection.java
new file mode 100644
index 0000000..887da70
--- /dev/null
+++ b/jni/Mapper/Db/Connection.java
@@ -0,0 +1,122 @@
+
+package Mapper.Db;
+
+import Mapper.PropertyValue;
+
+public class Connection
+{
+    /*! Describes what happens when the range boundaries are exceeded. */
+    public static final int BA_NONE  = 0;
+    public static final int BA_MUTE  = 1;
+    public static final int BA_CLAMP = 2;
+    public static final int BA_FOLD  = 3;
+    public static final int BA_WRAP  = 4;
+
+    /*! Describes the connection mode. */
+    public static final int MO_UNDEFINED  = 0;
+    public static final int MO_BYPASS     = 1;
+    public static final int MO_LINEAR     = 2;
+    public static final int MO_EXPRESSION = 3;
+    public static final int MO_CALIBRATE  = 4;
+    public static final int MO_REVERSE    = 5;
+
+    /*! Describes the voice-stealing mode for instances. */
+    public static final int IN_UNDEFINED    = 0;
+    public static final int IN_STEAL_OLDEST = 1;
+    public static final int IN_STEAL_NEWEST = 2;
+
+    public Connection(long conprops) {
+        _conprops = conprops;
+
+        srcName = mdb_connection_get_src_name(_conprops);
+        destName = mdb_connection_get_dest_name(_conprops);
+
+        srcType = mdb_connection_get_src_type(_conprops);
+        destType = mdb_connection_get_dest_type(_conprops);
+
+        srcLength = mdb_connection_get_src_length(_conprops);
+        destLength = mdb_connection_get_dest_length(_conprops);
+
+        boundMin = mdb_connection_get_bound_min(_conprops);
+        boundMax = mdb_connection_get_bound_max(_conprops);
+
+        srcMin = mdb_connection_get_src_min(_conprops);
+        srcMax = mdb_connection_get_src_max(_conprops);
+        destMin = mdb_connection_get_dest_min(_conprops);
+        destMax = mdb_connection_get_dest_max(_conprops);
+
+        mode = mdb_connection_get_mode(_conprops);
+        expression = mdb_connection_get_expression(_conprops);
+    }
+
+    public Connection(String _srcName, String _destName) {
+        srcName = _srcName;
+        destName = _destName;
+        srcType = 0;
+        destType = 0;
+        srcLength = -1;
+        destLength = -1;
+        boundMin = -1;
+        boundMax = -1;
+        srcMin = null;
+        srcMax = null;
+        destMin = null;
+        destMax = null;
+        mode = -1;
+        expression = null;
+    }
+
+    public Connection() {
+        this(null, null);
+    }
+
+    public String srcName;
+    private native String mdb_connection_get_src_name(long p);
+
+    public String destName;
+    private native String mdb_connection_get_dest_name(long p);
+
+    public char srcType;
+    private native char mdb_connection_get_src_type(long p);
+
+    public char destType;
+    private native char mdb_connection_get_dest_type(long p);
+
+    public int srcLength;
+    private native int mdb_connection_get_src_length(long p);
+
+    public int destLength;
+    private native int mdb_connection_get_dest_length(long p);
+
+    public int boundMin;
+    private native int mdb_connection_get_bound_min(long p);
+
+    public int boundMax;
+    private native int mdb_connection_get_bound_max(long p);
+
+    public PropertyValue srcMin;
+    private native PropertyValue mdb_connection_get_src_min(long p);
+
+    public PropertyValue srcMax;
+    private native PropertyValue mdb_connection_get_src_max(long p);
+
+    public PropertyValue destMin;
+    private native PropertyValue mdb_connection_get_dest_min(long p);
+
+    public PropertyValue destMax;
+    private native PropertyValue mdb_connection_get_dest_max(long p);
+
+    public int mode;
+    private native int mdb_connection_get_mode(long p);
+
+    public String expression;
+    private native String mdb_connection_get_expression(long p);
+
+    public PropertyValue property(String property) {
+        return mapper_db_connection_property_lookup(_conprops, property);
+    }
+    private native PropertyValue mapper_db_connection_property_lookup(
+        long p, String property);
+
+    private long _conprops;
+}
diff --git a/jni/Mapper/Db/ConnectionCollection.java b/jni/Mapper/Db/ConnectionCollection.java
new file mode 100644
index 0000000..b2f6c9e
--- /dev/null
+++ b/jni/Mapper/Db/ConnectionCollection.java
@@ -0,0 +1,17 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class ConnectionCollection implements Iterable<Mapper.Db.Connection>
+{
+    public ConnectionCollection(long conprops_p) {
+        _conprops_p = conprops_p;
+    }
+
+    @Override
+    public Iterator<Mapper.Db.Connection> iterator() {
+        return new ConnectionIterator(_conprops_p);
+    }
+    private long _conprops_p;
+}
diff --git a/jni/Mapper/Db/ConnectionIterator.java b/jni/Mapper/Db/ConnectionIterator.java
new file mode 100644
index 0000000..779cf66
--- /dev/null
+++ b/jni/Mapper/Db/ConnectionIterator.java
@@ -0,0 +1,37 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class ConnectionIterator implements Iterator<Mapper.Db.Connection> {
+    public ConnectionIterator(long conprops_p) {
+        _conprops_p = conprops_p;
+    }
+
+    protected void finalize() {
+        mdb_connection_done(_conprops_p);
+    }
+    private native void mdb_connection_done(long ptr);
+
+    @Override
+    public Mapper.Db.Connection next()
+    {
+        if (_conprops_p != 0) {
+            long temp = mdb_deref(_conprops_p);
+            _conprops_p = mdb_connection_next(_conprops_p);
+            return new Mapper.Db.Connection(temp);
+        }
+        else
+            return null;
+    }
+    private native long mdb_deref(long ptr);
+    private native long mdb_connection_next(long ptr);
+
+    @Override
+    public boolean hasNext() { return _conprops_p != 0; }
+
+    @Override
+    public void remove() {}
+
+    private long _conprops_p;
+}
diff --git a/jni/Mapper/Db/ConnectionListener.java b/jni/Mapper/Db/ConnectionListener.java
new file mode 100644
index 0000000..d44209c
--- /dev/null
+++ b/jni/Mapper/Db/ConnectionListener.java
@@ -0,0 +1,7 @@
+
+package Mapper.Db;
+
+public class ConnectionListener {
+    public void onEvent(Mapper.Db.Connection record,
+                        int action) {};
+}
diff --git a/jni/Mapper/Db/Device.java b/jni/Mapper/Db/Device.java
new file mode 100644
index 0000000..8a25ad4
--- /dev/null
+++ b/jni/Mapper/Db/Device.java
@@ -0,0 +1,82 @@
+
+package Mapper.Db;
+
+import Mapper.PropertyValue;
+import Mapper.TimeTag;
+
+public class Device
+{
+    public Device(long devprops) {
+        _devprops = devprops;
+
+        _name = mdb_device_get_name(_devprops);
+        _ordinal = mdb_device_get_ordinal(_devprops);
+        _host = mdb_device_get_host(_devprops);
+        _port = mdb_device_get_port(_devprops);
+
+        _n_inputs = mdb_device_get_num_inputs(_devprops);
+        _n_outputs = mdb_device_get_num_outputs(_devprops);
+        _n_links_in = mdb_device_get_num_links_in(_devprops);
+        _n_links_out = mdb_device_get_num_links_out(_devprops);
+        _n_connections_in = mdb_device_get_num_connections_in(_devprops);
+        _n_connections_out = mdb_device_get_num_connections_out(_devprops);
+        _version = mdb_device_get_version(_devprops);
+    }
+
+    private String _name;
+    public String name() { return _name; }
+    private native String mdb_device_get_name(long p);
+
+    int _ordinal;
+    public int ordinal() { return _ordinal; }
+    private native int mdb_device_get_ordinal(long p);
+
+    private String _host;
+    public String host() { return _host; }
+    private native String mdb_device_get_host(long p);
+
+    int _port;
+    public int port() { return _port; }
+    private native int mdb_device_get_port(long p);
+
+    int _n_inputs;
+    public int numInputs() { return _n_inputs; }
+    private native int mdb_device_get_num_inputs(long p);
+
+    int _n_outputs;
+    public int munOutputs() { return _n_outputs; }
+    private native int mdb_device_get_num_outputs(long p);
+
+    int _n_links_in;
+    public int numLinksIn() { return _n_links_in; }
+    private native int mdb_device_get_num_links_in(long p);
+
+    int _n_links_out;
+    public int numLinksOut() { return _n_links_out; }
+    private native int mdb_device_get_num_links_out(long p);
+
+    int _n_connections_in;
+    public int numConnectionsIn() { return _n_connections_in; }
+    private native int mdb_device_get_num_connections_in(long p);
+
+    int _n_connections_out;
+    public int numConnectionsOut() { return _n_connections_out; }
+    private native int mdb_device_get_num_connections_out(long p);
+
+    int _version;
+    public int version() { return _version; }
+    private native int mdb_device_get_version(long p);
+
+    public TimeTag synced() {
+        return mdb_device_get_synced(_devprops);
+    }
+    private native TimeTag mdb_device_get_synced(long p);
+
+    public PropertyValue property(String property) {
+        return mdb_device_property_lookup(_devprops, property);
+    }
+    private native PropertyValue mdb_device_property_lookup(
+        long p, String property);
+
+    private long _devprops;
+}
diff --git a/jni/Mapper/Db/DeviceCollection.java b/jni/Mapper/Db/DeviceCollection.java
new file mode 100644
index 0000000..c4099be
--- /dev/null
+++ b/jni/Mapper/Db/DeviceCollection.java
@@ -0,0 +1,17 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class DeviceCollection implements Iterable<Mapper.Db.Device>
+{
+    public DeviceCollection(long devprops_p) {
+        _devprops_p = devprops_p;
+    }
+
+    @Override
+    public Iterator<Mapper.Db.Device> iterator() {
+        return new DeviceIterator(_devprops_p);
+    }
+    private long _devprops_p;
+}
diff --git a/jni/Mapper/Db/DeviceIterator.java b/jni/Mapper/Db/DeviceIterator.java
new file mode 100644
index 0000000..b942f31
--- /dev/null
+++ b/jni/Mapper/Db/DeviceIterator.java
@@ -0,0 +1,37 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class DeviceIterator implements Iterator<Mapper.Db.Device> {
+    public DeviceIterator(long devprops_p) {
+        _devprops_p = devprops_p;
+    }
+
+    protected void finalize() {
+        mdb_device_done(_devprops_p);
+    }
+    private native void mdb_device_done(long ptr);
+
+    @Override
+    public Mapper.Db.Device next()
+    {
+        if (_devprops_p != 0) {
+            long temp = mdb_deref(_devprops_p);
+            _devprops_p = mdb_device_next(_devprops_p);
+            return new Mapper.Db.Device(temp);
+        }
+        else
+            return null;
+    }
+    private native long mdb_deref(long ptr);
+    private native long mdb_device_next(long ptr);
+
+    @Override
+    public boolean hasNext() { return _devprops_p != 0; }
+
+    @Override
+    public void remove() {}
+
+    private long _devprops_p;
+}
diff --git a/jni/Mapper/Db/DeviceListener.java b/jni/Mapper/Db/DeviceListener.java
new file mode 100644
index 0000000..7f63419
--- /dev/null
+++ b/jni/Mapper/Db/DeviceListener.java
@@ -0,0 +1,7 @@
+
+package Mapper.Db;
+
+public class DeviceListener {
+    public void onEvent(Mapper.Db.Device record,
+                        int action) {};
+}
diff --git a/jni/Mapper/Db/Link.java b/jni/Mapper/Db/Link.java
new file mode 100644
index 0000000..b1b2b09
--- /dev/null
+++ b/jni/Mapper/Db/Link.java
@@ -0,0 +1,65 @@
+
+package Mapper.Db;
+
+import Mapper.PropertyValue;
+
+public class Link
+{
+    public Link(long linkprops) {
+        _linkprops = linkprops;
+
+        _src_name = mdb_link_get_src_name(_linkprops);
+        _dest_name = mdb_link_get_dest_name(_linkprops);
+
+        _src_host = mdb_link_get_src_host(_linkprops);
+        _src_port = mdb_link_get_src_port(_linkprops);
+        _dest_host = mdb_link_get_dest_host(_linkprops);
+        _dest_port = mdb_link_get_dest_port(_linkprops);
+
+        _num_scopes = mdb_link_get_num_scopes(_linkprops);
+        _scope_names = mdb_link_get_scope_names(_linkprops);
+    }
+
+    private String _src_name;
+    public String srcName() { return _src_name; }
+    private native String mdb_link_get_src_name(long p);
+
+    private String _dest_name;
+    public String destName() { return _dest_name; }
+    private native String mdb_link_get_dest_name(long p);
+
+    private String _src_host;
+    public String srcHost() { return _src_host; }
+    private native String mdb_link_get_src_host(long p);
+
+    int _src_port;
+    public int srcPort() { return _src_port; }
+    private native int mdb_link_get_src_port(long p);
+
+    private String _dest_host;
+    public String destHost() { return _dest_host; }
+    private native String mdb_link_get_dest_host(long p);
+
+    int _dest_port;
+    public int destPort() { return _dest_port; }
+    private native int mdb_link_get_dest_port(long p);
+
+    int _num_scopes;
+    public int numScopes() { return _num_scopes; }
+    private native int mdb_link_get_num_scopes(long p);
+
+    PropertyValue _scope_names;
+    public PropertyValue scopeNames() {
+        _scope_names = mdb_link_get_scope_names(_linkprops);
+        return _scope_names;
+    }
+    private native PropertyValue mdb_link_get_scope_names(long p);
+
+    public PropertyValue property(String property) {
+        return mapper_db_link_property_lookup(_linkprops, property);
+    }
+    private native PropertyValue mapper_db_link_property_lookup(
+        long p, String property);
+
+    private long _linkprops;
+}
diff --git a/jni/Mapper/Db/LinkCollection.java b/jni/Mapper/Db/LinkCollection.java
new file mode 100644
index 0000000..502c41f
--- /dev/null
+++ b/jni/Mapper/Db/LinkCollection.java
@@ -0,0 +1,17 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class LinkCollection implements Iterable<Mapper.Db.Link>
+{
+    public LinkCollection(long linkprops_p) {
+        _linkprops_p = linkprops_p;
+    }
+
+    @Override
+    public Iterator<Mapper.Db.Link> iterator() {
+        return new LinkIterator(_linkprops_p);
+    }
+    private long _linkprops_p;
+}
diff --git a/jni/Mapper/Db/LinkIterator.java b/jni/Mapper/Db/LinkIterator.java
new file mode 100644
index 0000000..b4aa046
--- /dev/null
+++ b/jni/Mapper/Db/LinkIterator.java
@@ -0,0 +1,37 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class LinkIterator implements Iterator<Mapper.Db.Link> {
+    public LinkIterator(long linkprops_p) {
+        _linkprops_p = linkprops_p;
+    }
+
+    protected void finalize() {
+        mdb_link_done(_linkprops_p);
+    }
+    private native void mdb_link_done(long ptr);
+
+    @Override
+    public Mapper.Db.Link next()
+    {
+        if (_linkprops_p != 0) {
+            long temp = mdb_deref(_linkprops_p);
+            _linkprops_p = mdb_link_next(_linkprops_p);
+            return new Mapper.Db.Link(temp);
+        }
+        else
+            return null;
+    }
+    private native long mdb_deref(long ptr);
+    private native long mdb_link_next(long ptr);
+
+    @Override
+    public boolean hasNext() { return _linkprops_p != 0; }
+
+    @Override
+    public void remove() {}
+
+    private long _linkprops_p;
+}
diff --git a/jni/Mapper/Db/LinkListener.java b/jni/Mapper/Db/LinkListener.java
new file mode 100644
index 0000000..eb61445
--- /dev/null
+++ b/jni/Mapper/Db/LinkListener.java
@@ -0,0 +1,7 @@
+
+package Mapper.Db;
+
+public class LinkListener {
+    public void onEvent(Mapper.Db.Link record,
+                        int action) {};
+}
diff --git a/jni/Mapper/Db/Signal.java b/jni/Mapper/Db/Signal.java
index de753ff..3b0db00 100644
--- a/jni/Mapper/Db/Signal.java
+++ b/jni/Mapper/Db/Signal.java
@@ -1,94 +1,72 @@
 
 package Mapper.Db;
 
-import Mapper.Device;
 import Mapper.PropertyValue;
 
 public class Signal
 {
-    public Signal(long sigprops, Device.Signal s) {
+    public Signal(long sigprops) {
         _sigprops = sigprops;
-        _signal = s;
-
-        _name = msig_db_signal_get_name(_sigprops);
-        _device_name = msig_db_signal_get_device_name(_sigprops);
-
-        _is_output = msig_db_signal_get_is_output(_sigprops);
-        _type = msig_db_signal_get_type(_sigprops);
-        _length = msig_db_signal_get_length(_sigprops);
-        _unit = msig_db_signal_get_unit(_sigprops);
-        _minimum = msig_db_signal_get_minimum(_sigprops);
-        _maximum = msig_db_signal_get_maximum(_sigprops);
-        _rate = msig_db_signal_get_rate(_sigprops);
+
+        _name = mdb_signal_get_name(_sigprops);
+        _device_name = mdb_signal_get_device_name(_sigprops);
+
+        _is_output = mdb_signal_get_is_output(_sigprops);
+        _type = mdb_signal_get_type(_sigprops);
+        _length = mdb_signal_get_length(_sigprops);
+        _unit = mdb_signal_get_unit(_sigprops);
+        _minimum = mdb_signal_get_minimum(_sigprops);
+        _maximum = mdb_signal_get_maximum(_sigprops);
+        _rate = mdb_signal_get_rate(_sigprops);
     }
 
     private String _name;
     public String name() { return _name; }
-    public void set_name(String _name) {
-        checkParents();
-        msig_db_signal_set_name(_sigprops, _name);
-        _name = msig_db_signal_get_name(_sigprops);
-    }
-    private native void msig_db_signal_set_name(long p, String name);
-    private native String msig_db_signal_get_name(long p);
+    private native String mdb_signal_get_name(long p);
 
     private String _device_name;
-    public String device_name() { return _device_name; }
-    private native String msig_db_signal_get_device_name(long p);
+    public String deviceName() { return _device_name; }
+    private native String mdb_signal_get_device_name(long p);
 
 	boolean _is_output;
-    public boolean is_output() { return _is_output; }
-    private native boolean msig_db_signal_get_is_output(long p);
+    public boolean isOutput() { return _is_output; }
+    private native boolean mdb_signal_get_is_output(long p);
 
     char _type;
     public char type() { return _type; }
-    private native char msig_db_signal_get_type(long p);
+    private native char mdb_signal_get_type(long p);
 
     int _length;
     public int length() { return _length; }
-    private native int msig_db_signal_get_length(long p);
+    private native int mdb_signal_get_length(long p);
 
     private String _unit;
     public String unit() { return _unit; }
-    public void set_unit(String _unit) {
-        checkParents();
-        msig_db_signal_set_unit(_sigprops, _unit);
-        _unit = msig_db_signal_get_unit(_sigprops);
-    }
-    private native void msig_db_signal_set_unit(long p, String unit);
-    private native String msig_db_signal_get_unit(long p);
+    private native String mdb_signal_get_unit(long p);
 
     PropertyValue _minimum;
     public PropertyValue minimum() {
-        _minimum = msig_db_signal_get_minimum(_sigprops);
+        _minimum = mdb_signal_get_minimum(_sigprops);
         return _minimum;
     }
-    private native PropertyValue msig_db_signal_get_minimum(long p);
+    private native PropertyValue mdb_signal_get_minimum(long p);
 
     PropertyValue _maximum;
     public PropertyValue maximum() {
-        _maximum = msig_db_signal_get_maximum(_sigprops);
+        _maximum = mdb_signal_get_maximum(_sigprops);
         return _maximum;
     }
-    private native PropertyValue msig_db_signal_get_maximum(long p);
+    private native PropertyValue mdb_signal_get_maximum(long p);
 
     double _rate;
     public double rate() { return _rate; }
-    private native double msig_db_signal_get_rate(long p);
+    private native double mdb_signal_get_rate(long p);
 
-    public PropertyValue property_lookup(String property) {
-        return mapper_db_signal_property_lookup(_sigprops, property);
+    public PropertyValue property(String property) {
+        return mdb_signal_property_lookup(_sigprops, property);
     }
-    private native PropertyValue mapper_db_signal_property_lookup(
+    private native PropertyValue mdb_signal_property_lookup(
         long p, String property);
 
-    private void checkParents() {
-        if (!_signal.valid())
-            throw new NullPointerException(
-                "Cannot set property for a Signal object that "
-                +"is associated with an invalid Device");
-    }
-
     private long _sigprops;
-    private Device.Signal _signal;
 }
diff --git a/jni/Mapper/Db/SignalCollection.java b/jni/Mapper/Db/SignalCollection.java
new file mode 100644
index 0000000..6af9922
--- /dev/null
+++ b/jni/Mapper/Db/SignalCollection.java
@@ -0,0 +1,17 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class SignalCollection implements Iterable<Mapper.Db.Signal>
+{
+    public SignalCollection(long sigprops_p) {
+        _sigprops_p = sigprops_p;
+    }
+
+    @Override
+    public Iterator<Mapper.Db.Signal> iterator() {
+        return new SignalIterator(_sigprops_p);
+    }
+    private long _sigprops_p;
+}
diff --git a/jni/Mapper/Db/SignalIterator.java b/jni/Mapper/Db/SignalIterator.java
new file mode 100644
index 0000000..2a44402
--- /dev/null
+++ b/jni/Mapper/Db/SignalIterator.java
@@ -0,0 +1,37 @@
+
+package Mapper.Db;
+
+import java.util.Iterator;
+
+public class SignalIterator implements Iterator<Mapper.Db.Signal> {
+    public SignalIterator(long sigprops_p) {
+        _sigprops_p = sigprops_p;
+    }
+
+    protected void finalize() {
+        mdb_signal_done(_sigprops_p);
+    }
+    private native void mdb_signal_done(long ptr);
+
+    @Override
+    public Mapper.Db.Signal next()
+    {
+        if (_sigprops_p != 0) {
+            long temp = mdb_deref(_sigprops_p);
+            _sigprops_p = mdb_signal_next(_sigprops_p);
+            return new Mapper.Db.Signal(temp);
+        }
+        else
+            return null;
+    }
+    private native long mdb_deref(long ptr);
+    private native long mdb_signal_next(long ptr);
+
+    @Override
+    public boolean hasNext() { return _sigprops_p != 0; }
+
+    @Override
+    public void remove() {}
+
+    private long _sigprops_p;
+}
diff --git a/jni/Mapper/Db/SignalListener.java b/jni/Mapper/Db/SignalListener.java
new file mode 100644
index 0000000..38d5809
--- /dev/null
+++ b/jni/Mapper/Db/SignalListener.java
@@ -0,0 +1,7 @@
+
+package Mapper.Db;
+
+public class SignalListener {
+    public void onEvent(Mapper.Db.Signal record,
+                        int action) {};
+}
diff --git a/jni/Mapper/Device.java b/jni/Mapper/Device.java
index 8eef315..43feee3 100644
--- a/jni/Mapper/Device.java
+++ b/jni/Mapper/Device.java
@@ -7,6 +7,12 @@ import Mapper.Db.*;
 
 public class Device
 {
+    /*! The set of possible actions on a local device link or
+     *  connection. */
+    public static final int MDEV_LOCAL_ESTABLISHED  = 0;
+    public static final int MDEV_LOCAL_MODIFIED     = 1;
+    public static final int MDEV_LOCAL_DESTROYED    = 2;
+
     public Device(String name) {
         _device = mdev_new(name, 0);
     }
@@ -24,14 +30,17 @@ public class Device
         return mdev_poll(_device, timeout);
     }
 
+    public int poll() {
+        return mdev_poll(_device, 0);
+    }
+
     public class Signal {
 
         /*! Describes the voice-stealing mode for instances.
          *  Arguments to set_instance_allocation_mode(). */
-        public static final int IN_UNDEFINED = 1;
-        public static final int IN_STEAL_OLDEST = 2;
-        public static final int IN_STEAL_NEWEST = 3;
-        public static final int N_MAPPER_INSTANCE_ALLOCATION_TYPES = 4;
+        public static final int IN_UNDEFINED                        = 0;
+        public static final int IN_STEAL_OLDEST                     = 1;
+        public static final int IN_STEAL_NEWEST                     = 2;
 
         private Signal(long s, Device d) { _signal = s; _device = d; }
 
@@ -40,34 +49,34 @@ public class Device
             checkDevice();
             return msig_name(_signal);
         }
-        public String full_name()
+        public String fullName()
         {
             checkDevice();
             return msig_full_name(_signal);
         }
-        public boolean is_output()
+        public boolean isOutput()
         {
             checkDevice();
             return msig_is_output(_signal);
         }
-        public void set_minimum(PropertyValue p) {
+        public void setMinimum(PropertyValue p) {
             checkDevice();
             msig_set_property(_signal, new String("min"), p);
         }
-        public void set_maximum(PropertyValue p) {
+        public void setMaximum(PropertyValue p) {
             checkDevice();
             msig_set_property(_signal, new String("max"), p);
         }
-        public void set_rate(double rate) {
+        public void setRate(double rate) {
             checkDevice();
             msig_set_rate(_signal, rate);
         }
-        public void set_property(String property, PropertyValue p)
+        public void setProperty(String property, PropertyValue p)
         {
             checkDevice();
             msig_set_property(_signal, property, p);
         }
-        public void remove_property(String property)
+        public void removeProperty(String property)
         {
             checkDevice();
             msig_remove_property(_signal, property);
@@ -82,103 +91,126 @@ public class Device
                                               PropertyValue p);
         private native void msig_remove_property(long sig, String property);
 
-        public native void set_instance_event_callback(
+        public native void setInstanceEventCallback(
             InstanceEventListener handler, int flags);
-        public native void set_callback(InputListener handler);
-
-        public native void set_instance_callback(int instance_id,
-                                                 InputListener cb);
-        public native InputListener get_instance_callback(int instance_id);
-
-        public native int reserve_instances(int num);
-        public native int reserve_instances(int[] ids);
-        public native int reserve_instances(int num, InputListener cb);
-        public native int reserve_instances(int[] ids, InputListener cb);
-        public native void release_instance(int instance_id, TimeTag tt);
-        public void release_instance(int instance_id)
-            { release_instance(instance_id, null); }
-
-        public native Integer oldest_active_instance();
-        public native Integer newest_active_instance();
-
-        public native void match_instances(Signal from, Signal to,
-                                           int instance_id);
-        public native int num_active_instances();
-        public native int num_reserved_instances();
-        public native int active_instance_id(int index);
-        public native void set_instance_allocation_mode(int mode);
-        public native int instance_allocation_mode();
-
-        public native int num_connections();
-
-        public native void update(int value);
-        public native void update(float value);
-        public native void update(double value);
-        public native void update(int[] value);
-        public native void update(float[] value);
-        public native void update(double[] value);
-
-        public native void update(int value, TimeTag tt);
-        public native void update(float value, TimeTag tt);
-        public native void update(double value, TimeTag tt);
-        public native void update(int[] value, TimeTag tt);
-        public native void update(float[] value, TimeTag tt);
-        public native void update(double[] value, TimeTag tt);
-
-        public void update_instance(int instance_id, int value)
-            { update_instance(instance_id, value, null); }
-        public void update_instance(int instance_id, float value)
-            { update_instance(instance_id, value, null); }
-        public void update_instance(int instance_id, double value)
-            { update_instance(instance_id, value, null); }
-        public void update_instance(int instance_id, int[] value)
-            { update_instance(instance_id, value, null); }
-        public void update_instance(int instance_id, float[] value)
-            { update_instance(instance_id, value, null); }
-        public void update_instance(int instance_id, double[] value)
-            { update_instance(instance_id, value, null); }
-
-        public native void update_instance(int instance_id,
-                                           int value, TimeTag tt);
-        public native void update_instance(int instance_id,
-                                           float value, TimeTag tt);
-        public native void update_instance(int instance_id,
-                                           double value, TimeTag tt);
-        public native void update_instance(int instance_id,
-                                           int[] value, TimeTag tt);
-        public native void update_instance(int instance_id,
-                                           float[] value, TimeTag tt);
-        public native void update_instance(int instance_id,
-                                           double[] value, TimeTag tt);
-
-        public native boolean value(int[] value, TimeTag tt);
-        public native boolean value(float[] value, TimeTag tt);
-        public native boolean value(double[] value, TimeTag tt);
+        public native void setCallback(InputListener handler);
+
+        public native void setInstanceCallback(int instanceId,
+                                               InputListener cb);
+        public native InputListener getInstanceCallback(int instanceId);
+
+        public native int reserveInstances(int[] ids, int num, InputListener cb);
+        public int reserveInstances(int num)
+            { return reserveInstances(null, num, null); }
+        public int reserveInstances(int[] ids)
+            { return reserveInstances(ids, 0, null); }
+        public int reserveInstances(int num, InputListener cb)
+            { return reserveInstances(null, num, cb); }
+        public int reserveInstances(int[] ids, InputListener cb)
+            { return reserveInstances(ids, 0, cb); }
+
+
+        public native void releaseInstance(int instanceId, TimeTag tt);
+        public void releaseInstance(int instanceId)
+            { releaseInstance(instanceId, null); }
+        public native void removeInstance(int num);
+
+        public native Integer oldestActiveInstance();
+        public native Integer newestActiveInstance();
+
+        public native void matchInstances(Signal from, Signal to,
+                                          int instanceId);
+        public native int numActiveInstances();
+        public native int numReservedInstances();
+        public native int activeInstanceId(int index);
+        public native void setInstanceAllocationMode(int mode);
+        public native int instanceAllocationMode();
+
+        public native int numConnections();
+
+        public native void updateInstance(int instanceId,
+                                          int value, TimeTag tt);
+        public native void updateInstance(int instanceId,
+                                          float value, TimeTag tt);
+        public native void updateInstance(int instanceId,
+                                          double value, TimeTag tt);
+        public native void updateInstance(int instanceId,
+                                          int[] value, TimeTag tt);
+        public native void updateInstance(int instanceId,
+                                          float[] value, TimeTag tt);
+        public native void updateInstance(int instanceId,
+                                          double[] value, TimeTag tt);
+
+        public void updateInstance(int instanceId, int value)
+            { updateInstance(instanceId, value, null); }
+        public void updateInstance(int instanceId, float value)
+            { updateInstance(instanceId, value, null); }
+        public void updateInstance(int instanceId, double value)
+            { updateInstance(instanceId, value, null); }
+        public void updateInstance(int instanceId, int[] value)
+            { updateInstance(instanceId, value, null); }
+        public void updateInstance(int instanceId, float[] value)
+            { updateInstance(instanceId, value, null); }
+        public void updateInstance(int instanceId, double[] value)
+            { updateInstance(instanceId, value, null); }
+
+        public void update(int value)
+            { updateInstance(0, value, null); }
+        public void update(float value)
+            { updateInstance(0, value, null); }
+        public void update(double value)
+            { updateInstance(0, value, null); }
+        public void update(int[] value)
+            { updateInstance(0, value, null); }
+        public void update(float[] value)
+            { updateInstance(0, value, null); }
+        public void update(double[] value)
+            { updateInstance(0, value, null); }
+
+        public void update(int value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+        public void update(float value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+        public void update(double value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+        public void update(int[] value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+        public void update(float[] value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+        public void update(double[] value, TimeTag tt)
+            { updateInstance(0, value, tt); }
+
+        public native boolean instanceValue(int instanceId,
+                                            int[] value, TimeTag tt);
+        public native boolean instanceValue(int instanceId,
+                                            float[] value, TimeTag tt);
+        public native boolean instanceValue(int instanceId,
+                                            double[] value, TimeTag tt);
+
+        public boolean instanceValue(int instanceId, int[] value)
+            { return instanceValue(instanceId, value, null); }
+        public boolean instanceValue(int instanceId, float[] value)
+            { return instanceValue(instanceId, value, null); }
+        public boolean instanceValue(int instanceId, double[] value)
+            { return instanceValue(instanceId, value, null); }
+
+        public boolean value(int[] value, TimeTag tt)
+            { return instanceValue(0, value, tt); }
+        public boolean value(float[] value, TimeTag tt)
+            { return instanceValue(0, value, tt); }
+        public boolean value(double[] value, TimeTag tt)
+            { return instanceValue(0, value, tt); }
 
         public boolean value(int[] value)
-            { return value(value, null); }
+            { return instanceValue(0, value, null); }
         public boolean value(float[] value)
-            { return value(value, null); }
+            { return instanceValue(0, value, null); }
         public boolean value(double[] value)
-            { return value(value, null); }
-
-        public native boolean instance_value(int instance_id,
-                                             int[] value, TimeTag tt);
-        public native boolean instance_value(int instance_id,
-                                             float[] value, TimeTag tt);
-        public native boolean instance_value(int instance_id,
-                                             double[] value, TimeTag tt);
-
-        public boolean instance_value(int instance_id, int[] value)
-            { return instance_value(instance_id, value, null); }
-        public boolean instance_value(int instance_id, float[] value)
-            { return instance_value(instance_id, value, null); }
-        public boolean instance_value(int instance_id, double[] value)
-            { return instance_value(instance_id, value, null); }
-
-        public native int query_remotes(TimeTag tt);
-        public int query_remotes()
-            { return query_remotes(null); };
+            { return instanceValue(0, value, null); }
+
+        public native int queryRemotes(TimeTag tt);
+        public int queryRemotes()
+            { return queryRemotes(null); };
 
         public int index()
         {
@@ -186,7 +218,7 @@ public class Device
             if (_index==null) {
                 _index = new Integer(-1);
                 long msig = 0;
-                if (is_output())
+                if (isOutput())
                     msig = mdev_get_output_by_name(_device._device,
                                                    msig_name(_signal),
                                                    _index);
@@ -215,76 +247,76 @@ public class Device
         private Integer _index;
     };
 
-    public void remove_input(Signal sig)
+    public void removeInput(Signal sig)
     {
         mdev_remove_input(_device, sig._signal);
     }
 
-    public void remove_output(Signal sig)
+    public void removeOutput(Signal sig)
     {
         mdev_remove_output(_device, sig._signal);
     }
 
-    public int num_inputs()
+    public int numInputs()
     {
         return mdev_num_inputs(_device);
     }
 
-    public int num_outputs()
+    public int numOutputs()
     {
         return mdev_num_outputs(_device);
     }
 
-    public int num_links_in()
+    public int numLinksIn()
     {
         return mdev_num_links_in(_device);
     }
 
-    public int num_links_out()
+    public int numLinksOut()
     {
         return mdev_num_links_out(_device);
     }
 
-    public int num_connections_in()
+    public int numConnectionsIn()
     {
         return mdev_num_connections_in(_device);
     }
 
-    public int num_connections_out()
+    public int numConnectionsOut()
     {
         return mdev_num_connections_out(_device);
     }
 
-    public Signal get_input_by_name(String name)
+    public Signal getInput(String name)
     {
         long msig = mdev_get_input_by_name(_device, name, null);
         return msig==0 ? null : new Signal(msig, this);
     }
 
-    public Signal get_output_by_name(String name)
+    public Signal getOutput(String name)
     {
         long msig = mdev_get_output_by_name(_device, name, null);
         return msig==0 ? null : new Signal(msig, this);
     }
 
-    public Signal get_input_by_index(int index)
+    public Signal getInput(int index)
     {
         long msig = mdev_get_input_by_index(_device, index);
         return msig==0 ? null : new Signal(msig, this);
     }
 
-    public Signal get_output_by_index(int index)
+    public Signal getOutput(int index)
     {
         long msig = mdev_get_output_by_index(_device, index);
         return msig==0 ? null : new Signal(msig, this);
     }
 
-    public void set_property(String property, PropertyValue p)
+    public void setProperty(String property, PropertyValue p)
     {
         mdev_set_property(_device, property, p);
     }
 
-    public void remove_property(String property)
+    public void removeProperty(String property)
     {
         mdev_remove_property(_device, property);
     }
@@ -324,16 +356,21 @@ public class Device
         return mdev_id(_device);
     }
 
-    public void start_queue(TimeTag tt)
+    public void startQueue(TimeTag tt)
     {
         mdev_start_queue(_device, tt);
     }
 
-    public void send_queue(TimeTag tt)
+    public void sendQueue(TimeTag tt)
     {
         mdev_send_queue(_device, tt);
     }
 
+    public TimeTag now()
+    {
+        return mdev_now(_device);
+    }
+
     // Note: this is _not_ guaranteed to run, the user should still
     // call free() explicitly when the device is no longer needed.
     protected void finalize() throws Throwable {
@@ -361,6 +398,7 @@ public class Device
                                                 Integer index);
     private native long mdev_get_input_by_index(long _d, int index);
     private native long mdev_get_output_by_index(long _d, int index);
+    public native Mapper.Db.Device properties();
     private native void mdev_set_property(long _d, String property,
                                           PropertyValue p);
     private native void mdev_remove_property(long _d, String property);
@@ -373,20 +411,21 @@ public class Device
     private native int mdev_id(long _d);
     private native void mdev_start_queue(long _d, TimeTag tt);
     private native void mdev_send_queue(long _d, TimeTag tt);
+    private native TimeTag mdev_now(long _d);
 
-    public native Signal add_input(String name, int length, char type, String unit,
-                                   PropertyValue minimum, PropertyValue maximum,
-                                   InputListener handler);
+    public native Signal addInput(String name, int length, char type, String unit,
+                                  PropertyValue minimum, PropertyValue maximum,
+                                  InputListener handler);
 
-    public native Signal add_output(String name, int length, char type, String unit,
-                                    PropertyValue minimum, PropertyValue maximum);
+    public native Signal addOutput(String name, int length, char type, String unit,
+                                   PropertyValue minimum, PropertyValue maximum);
 
     private long _device;
     public boolean valid() {
         return _device != 0;
     }
 
-    static { 
+    static {
         System.loadLibrary(NativeLib.name);
-    } 
+    }
 }
diff --git a/jni/Mapper/InputListener.java b/jni/Mapper/InputListener.java
index 5cdd5ee..ca17f66 100644
--- a/jni/Mapper/InputListener.java
+++ b/jni/Mapper/InputListener.java
@@ -3,18 +3,15 @@ package Mapper;
 
 public class InputListener {
     public void onInput(Mapper.Device.Signal sig,
-                        Mapper.Db.Signal props,
-                        int instance_id,
+                        int instanceId,
                         float[] v,
                         TimeTag tt) {};
     public void onInput(Mapper.Device.Signal sig,
-                        Mapper.Db.Signal props,
-                        int instance_id,
+                        int instanceId,
                         int[] v,
                         TimeTag tt) {};
     public void onInput(Mapper.Device.Signal sig,
-                        Mapper.Db.Signal props,
-                        int instance_id,
+                        int instanceId,
                         double[] v,
                         TimeTag tt) {};
 }
diff --git a/jni/Mapper/InstanceEventListener.java b/jni/Mapper/InstanceEventListener.java
index 32e7732..34892d6 100644
--- a/jni/Mapper/InstanceEventListener.java
+++ b/jni/Mapper/InstanceEventListener.java
@@ -4,15 +4,14 @@ package Mapper;
 public class InstanceEventListener {
     /*! The set of possible actions on an instance, used to
      *  register callbacks to inform them of what is happening. */
-    public static final int IN_NEW = 0x01;
-    public static final int IN_UPSTREAM_RELEASE = 0x02;
+    public static final int IN_NEW                = 0x01;
+    public static final int IN_UPSTREAM_RELEASE   = 0x02;
     public static final int IN_DOWNSTREAM_RELEASE = 0x04;
-    public static final int IN_OVERFLOW = 0x08;
-    public static final int IN_ALL = 0xFF;
+    public static final int IN_OVERFLOW           = 0x08;
+    public static final int IN_ALL                = 0xFF;
 
     public void onEvent(Mapper.Device.Signal sig,
-                        Mapper.Db.Signal props,
-                        int instance_id,
+                        int instanceId,
                         int event,
                         TimeTag tt) {};
 }
diff --git a/jni/Mapper/LinkListener.java b/jni/Mapper/LinkListener.java
new file mode 100644
index 0000000..f66984d
--- /dev/null
+++ b/jni/Mapper/LinkListener.java
@@ -0,0 +1,13 @@
+
+package Mapper;
+
+public class LinkListener {
+    /*! The set of possible actions on a local device link or connection. */
+    public static final int MDEV_LOCAL_ESTABLISHED = 0;
+    public static final int MDEV_LOCAL_MODIFIED    = 1;
+    public static final int MDEV_LOCAL_DESTROYED   = 2;
+
+    public void onLink(Mapper.Device dev,
+                       Mapper.Db.Link link,
+                       int action) {};
+}
diff --git a/jni/Mapper/Monitor.java b/jni/Mapper/Monitor.java
new file mode 100644
index 0000000..e65fe78
--- /dev/null
+++ b/jni/Mapper/Monitor.java
@@ -0,0 +1,300 @@
+
+package Mapper;
+
+import Mapper.NativeLib;
+import Mapper.PropertyValue;
+import Mapper.TimeTag;
+import Mapper.Db.*;
+
+public class Monitor
+{
+    /*! Bit flags for coordinating monitor metadata subscriptions. */
+    public static final int SUB_NONE                    = 0x00;
+    public static final int SUB_DEVICE                  = 0x01;
+    public static final int SUB_DEVICE_INPUTS           = 0x03;
+    public static final int SUB_DEVICE_OUTPUTS          = 0x05;
+    public static final int SUB_DEVICE_SIGNALS          = 0x07;
+    public static final int SUB_DEVICE_LINKS            = 0x09;
+    public static final int SUB_DEVICE_CONNECTIONS_IN   = 0x21;
+    public static final int SUB_DEVICE_CONNECTIONS_OUT  = 0x41;
+    public static final int SUB_DEVICE_CONNECTIONS      = 0x61;
+    public static final int SUB_DEVICE_ALL              = 0xFF;
+
+    public Monitor(int autosubscribeFlags) {
+        _monitor = mmon_new(autosubscribeFlags);
+        Db = this.new Db(mmon_get_db(_monitor));
+    }
+    public Monitor() {
+        _monitor = mmon_new(0);
+        Db = this.new Db(mmon_get_db(_monitor));
+    }
+
+    public void free() {
+        if (_monitor!=0)
+            mmon_free(_monitor);
+        _monitor = 0;
+    }
+
+    public int poll(int timeout) {
+        return mmon_poll(_monitor, timeout);
+    }
+
+    public class Db {
+
+        /*! The set of possible actions on a db entity. */
+        public static final int MODIFIED = 0;
+        public static final int NEW      = 1;
+        public static final int REMOVED  = 2;
+
+        private Db(long d) {
+            _db = d;
+            _device_cb = null;
+            _signal_cb = null;
+            _link_cb = null;
+            _connection_cb = null;
+        }
+
+        private void checkMonitor() {
+            if (_monitor._monitor == 0)
+                throw new NullPointerException(
+                    "Db object associated with invalid Monitor");
+        }
+
+        public boolean valid() {
+            return _monitor._monitor != 0;
+        }
+
+        // Callbacks
+        public void addDeviceCallback(Mapper.Db.DeviceListener handler)
+        {
+            if (handler != _device_cb)
+                mdb_remove_device_callback(_db, _device_cb);
+            mdb_add_device_callback(_db, handler);
+            _device_cb = handler;
+        }
+        private native void mdb_add_device_callback(long db, Mapper.Db.DeviceListener handler);
+
+        public void removeDeviceCallback(Mapper.Db.DeviceListener handler)
+        {
+            mdb_remove_device_callback(_db, handler);
+        }
+        private native void mdb_remove_device_callback(long db, Mapper.Db.DeviceListener handler);
+
+        public void addSignalCallback(Mapper.Db.SignalListener handler)
+        {
+            if (handler != _signal_cb)
+                mdb_remove_signal_callback(_db, _signal_cb);
+            mdb_add_signal_callback(_db, handler);
+            _signal_cb = handler;
+        }
+        private native void mdb_add_signal_callback(long db, Mapper.Db.SignalListener handler);
+
+        public void removeSignalCallback(Mapper.Db.SignalListener handler)
+        {
+            mdb_remove_signal_callback(_db, handler);
+        }
+        private native void mdb_remove_signal_callback(long db, Mapper.Db.SignalListener handler);
+
+        public void addLinkCallback(Mapper.Db.LinkListener handler)
+        {
+            if (handler != _link_cb)
+                mdb_remove_link_callback(_db, _link_cb);
+            mdb_add_link_callback(_db, handler);
+            _link_cb = handler;
+        }
+        private native void mdb_add_link_callback(long _p, Mapper.Db.LinkListener handler);
+
+        public void removeLinkCallback(Mapper.Db.LinkListener handler)
+        {
+            mdb_remove_link_callback(_db, handler);
+        }
+        private native void mdb_remove_link_callback(long db, Mapper.Db.LinkListener handler);
+
+        public void addConnectionCallback(Mapper.Db.ConnectionListener handler)
+        {
+            if (handler != _connection_cb)
+                mdb_remove_connection_callback(_db, _connection_cb);
+            mdb_add_connection_callback(_db, handler);
+            _connection_cb = handler;
+        }
+        private native void mdb_add_connection_callback(long db, Mapper.Db.ConnectionListener handler);
+
+        public void removeConnectionCallback(Mapper.Db.ConnectionListener handler)
+        {
+            mdb_remove_connection_callback(_db, handler);
+        }
+        private native void mdb_remove_connection_callback(long db, Mapper.Db.ConnectionListener handler);
+
+        // Db.Device
+        public native Mapper.Db.DeviceCollection devices();
+        public native Mapper.Db.Device get_device(String deviceName);
+        public native Mapper.Db.DeviceCollection match_devices(String pattern);
+
+        // Db.Input
+        public native Mapper.Db.Signal getInput(String deviceName,
+                                                String signalName);
+        private native long mdb_inputs(long db, String deviceName);
+        public Mapper.Db.SignalCollection inputs() {
+            long _s = mdb_inputs(_db, null);
+            return (_s == 0) ? null : new Mapper.Db.SignalCollection(_s);
+        }
+        public Mapper.Db.SignalCollection inputs(String deviceName) {
+            long _s = mdb_inputs(_db, deviceName);
+            return (_s == 0) ? null : new Mapper.Db.SignalCollection(_s);
+        }
+        public native Mapper.Db.SignalCollection matchInputs(String deviceName,
+                                                             String signalPattern);
+
+        // Db.Output
+        public native Mapper.Db.Signal getOutput(String deviceName,
+                                                 String signalName);
+        private native long mdb_outputs(long db, String deviceName);
+        public Mapper.Db.SignalCollection outputs() {
+            long _s = mdb_outputs(_db, null);
+            return (_s == 0) ? null : new Mapper.Db.SignalCollection(_s);
+        }
+        public Mapper.Db.SignalCollection outputs(String deviceName) {
+            long _s = mdb_outputs(_db, deviceName);
+            return (_s == 0) ? null : new Mapper.Db.SignalCollection(_s);
+        }
+        public native Mapper.Db.SignalCollection matchOutputs(String deviceName,
+                                                              String signalPattern);
+
+        // Db.Link
+        public native Mapper.Db.Link getLink(String srcName, String destName);
+        private native Mapper.Db.LinkCollection mdb_links(long db, String deviceName);
+        public Mapper.Db.LinkCollection links(String deviceName)
+            { return mdb_links(_db, deviceName); }
+        public Mapper.Db.LinkCollection links()
+            { return mdb_links(_db, null); }
+        public native Mapper.Db.LinkCollection links(Mapper.Db.DeviceCollection src,
+                                                     Mapper.Db.DeviceCollection dest);
+        public native Mapper.Db.LinkCollection linksBySrc(String deviceName);
+        public native Mapper.Db.LinkCollection linksByDest(String deviceName);
+
+        // Db.Connection
+        private native Mapper.Db.ConnectionCollection mdb_connections(
+            long db, String deviceName);
+        public Mapper.Db.ConnectionCollection connections(String deviceName)
+            { return mdb_connections(_db, deviceName); }
+        public Mapper.Db.ConnectionCollection connections()
+            { return mdb_connections(_db, null); }
+        public native Mapper.Db.ConnectionCollection connections(
+            Mapper.Db.SignalCollection src, Mapper.Db.SignalCollection dest);
+        private native Mapper.Db.ConnectionCollection mdb_connections_by_src(
+            long db, String deviceName, String signalName);
+        public Mapper.Db.ConnectionCollection connectionsBySrc(String signalName)
+            { return mdb_connections_by_src(_db, null, signalName); }
+        public Mapper.Db.ConnectionCollection connectionsBySrc(String deviceName,
+                                                               String signalName)
+            { return mdb_connections_by_src(_db, deviceName, signalName); }
+        private native Mapper.Db.ConnectionCollection mdb_connections_by_dest(
+            long db, String deviceName, String signalName);
+        public Mapper.Db.ConnectionCollection connectionsByDest(String signalName)
+            { return mdb_connections_by_dest(_db, null, signalName); }
+        public Mapper.Db.ConnectionCollection connectionsByDest(String deviceName,
+                                                                String signalName)
+            { return mdb_connections_by_dest(_db, deviceName, signalName); }
+        public native Mapper.Db.Connection connectionBySignals(String srcName,
+                                                               String destName);
+        public native Mapper.Db.ConnectionCollection connectionsByDevices(
+            String srcDevice, String destDevice);
+
+        private long _db;
+        private Monitor _monitor;
+
+        // TODO: enable multiple listeners
+        private Mapper.Db.DeviceListener _device_cb;
+        private Mapper.Db.SignalListener _signal_cb;
+        private Mapper.Db.LinkListener _link_cb;
+        private Mapper.Db.ConnectionListener _connection_cb;
+    };
+
+    public void subscribe(String deviceName, int subscribeFlags, int timeout)
+    {
+        mmon_subscribe(_monitor, deviceName, subscribeFlags, timeout);
+    }
+
+    public void unsubscribe(String deviceName)
+    {
+        mmon_unsubscribe(_monitor, deviceName);
+    }
+
+    public void link(String sourceDevice, String destDevice,
+                     Mapper.Db.Link props)
+    {
+        mmon_link(_monitor, sourceDevice, destDevice, props);
+    }
+
+    public void unlink(String sourceDevice, String destDevice)
+    {
+        mmon_unlink(_monitor, sourceDevice, destDevice);
+    }
+
+    public void connect(String sourceSignal, String destSignal,
+                        Mapper.Db.Connection props)
+    {
+        mmon_connect_or_mod(_monitor, sourceSignal, destSignal, props, 0);
+    }
+
+    public void disconnect(String sourceSignal, String destSignal)
+    {
+        mmon_disconnect(_monitor, sourceSignal, destSignal);
+    }
+
+    public void modifyConnection(String sourceSignal, String destSignal,
+                                 Mapper.Db.Connection props)
+    {
+        mmon_connect_or_mod(_monitor, sourceSignal, destSignal, props, 1);
+    }
+
+    public void autosubscribe(int autosubscribeFlags)
+    {
+        mmon_autosubscribe(_monitor, autosubscribeFlags);
+    }
+
+    public TimeTag now()
+    {
+        return mmon_now(_monitor);
+    }
+
+    // Note: this is _not_ guaranteed to run, the user should still
+    // call free() explicitly when the monitor is no longer needed.
+    protected void finalize() throws Throwable {
+        try {
+            free();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private native long mmon_new(int autosubscribe_flags);
+    private native long mmon_get_db(long db);
+    private native void mmon_free(long db);
+    private native int mmon_poll(long db, int timeout);
+    private native void mmon_subscribe(long db, String device_name,
+                                       int subscribe_flags, int timeout);
+    private native void mmon_unsubscribe(long db, String device_name);
+    private native void mmon_link(long db, String source_device,
+                                  String dest_device, Mapper.Db.Link props);
+    private native void mmon_unlink(long db, String source_device,
+                                    String dest_device);
+    private native void mmon_connect_or_mod(long db, String source_signal,
+                                            String dest_signal,
+                                            Mapper.Db.Connection props,
+                                            int modify);
+    private native void mmon_disconnect(long db, String source_signal,
+                                        String dest_signal);
+    private native void mmon_autosubscribe(long db, int autosubscribe_flags);
+    private native TimeTag mmon_now(long db);
+
+    private long _monitor;
+    public Mapper.Monitor.Db Db;
+    public boolean valid() {
+        return _monitor != 0;
+    }
+
+    static {
+        System.loadLibrary(NativeLib.name);
+    }
+}
diff --git a/jni/Mapper/TimeTag.java b/jni/Mapper/TimeTag.java
index 5c5ef89..4e839ff 100644
--- a/jni/Mapper/TimeTag.java
+++ b/jni/Mapper/TimeTag.java
@@ -7,6 +7,7 @@ public class TimeTag
     public long frac;
 
     public static final TimeTag NOW = new TimeTag(0, 1);
+    private static double multiplier = (double)1.0/((double)((long)1<<32));
 
     public TimeTag(long _sec, long _frac)
     {
@@ -16,6 +17,13 @@ public class TimeTag
 
     public TimeTag(Double secondsSinceEpoch)
     {
-        // TO DO
+        sec = (long)Math.floor(secondsSinceEpoch);
+        secondsSinceEpoch -= sec;
+        frac = (long)(secondsSinceEpoch*(double)((long)1<<32));
+    }
+
+    public double getDouble()
+    {
+        return (double)sec + (double)frac * multiplier;
     }
 }
diff --git a/jni/TestInstances/TestInstances.pde b/jni/TestInstances/TestInstances.pde
index 4aff410..dd309cb 100644
--- a/jni/TestInstances/TestInstances.pde
+++ b/jni/TestInstances/TestInstances.pde
@@ -13,7 +13,7 @@ Circle circles[] = new Circle[5];
 int bs = 15;
 int count = 0;
 
-Mapper.Device dev = new Mapper.Device("TestInstances", 9000);
+Mapper.Device dev = new Mapper.Device("TestInstances");
 
 Mapper.Device.Signal sig_x_in = null;
 Mapper.Device.Signal sig_y_in = null;
@@ -30,34 +30,33 @@ void setup()
 
   /* Note: null for the InputListener, since we are specifying
    * this later per-instance. */
-  sig_x_in = dev.add_input("x", 1, 'i', "pixels",
-                           (double)0.0, (double)width, null);
-  sig_y_in = dev.add_input("y", 1, 'i', "pixels",
-                           (double)0.0, (double)width, null);
+  sig_x_in = dev.addInput("x", 1, 'i', "pixels",
+                          new PropertyValue(0), new PropertyValue(width), null);
+  sig_y_in = dev.addInput("y", 1, 'i', "pixels",
+                          new PropertyValue(0), new PropertyValue(height), null);
 
-  sig_x_out = dev.add_output("x", 1, 'i', "pixels",
-                             (double)0.0, (double)width);
-  sig_y_out = dev.add_output("y", 1, 'i', "pixels",
-                             (double)0.0, (double)width);
+  sig_x_out = dev.addOutput("x", 1, 'i', "pixels",
+                            new PropertyValue(0), new PropertyValue(width));
+  sig_y_out = dev.addOutput("y", 1, 'i', "pixels",
+                            new PropertyValue(0), new PropertyValue(height));
 
   Mapper.InstanceEventListener evin = new Mapper.InstanceEventListener() {
     public void onEvent(Mapper.Device.Signal sig,
-                 Mapper.Db.Signal props,
-                 int instance_id,
-                 int event,
-                 TimeTag tt) {
-      sig_x_in.set_instance_callback(instance_id, circles[instance_id-1].lx);
-      sig_y_in.set_instance_callback(instance_id, circles[instance_id-1].ly);
+                        int instanceId,
+                        int event,
+                        TimeTag tt) {
+      sig_x_in.setInstanceCallback(instanceId, circles[instanceId-1].lx);
+      sig_y_in.setInstanceCallback(instanceId, circles[instanceId-1].ly);
     };
   };
 
-  sig_x_in.set_instance_event_callback(evin,
+  sig_x_in.setInstanceEventCallback(evin,
     Mapper.InstanceEventListener.IN_ALL);
 
-  sig_x_in.reserve_instances(circles.length);
-  sig_y_in.reserve_instances(circles.length);
-  sig_x_out.reserve_instances(circles.length);
-  sig_y_out.reserve_instances(circles.length);
+  sig_x_in.reserveInstances(circles.length);
+  sig_y_in.reserveInstances(circles.length);
+  sig_x_out.reserveInstances(circles.length);
+  sig_y_out.reserveInstances(circles.length);
 
   for (int i=0; i < circles.length; i++) {
     circles[i] = new Circle(Math.random()*(width-bs*2)+bs,
@@ -130,16 +129,14 @@ class Circle
     /* Add listeners for our instance */
     lx = new Mapper.InputListener() {
           void onInput(Mapper.Device.Signal sig,
-                       Mapper.Db.Signal props,
-                       int instance_id, int[] v, TimeTag tt) {
+                       int instanceId, int[] v, TimeTag tt) {
             if (v!=null)
               bx = v[0];
           }};
 
     ly = new Mapper.InputListener() {
           void onInput(Mapper.Device.Signal sig,
-                       Mapper.Db.Signal props,
-                       int instance_id, int[] v, TimeTag tt) {
+                       int instanceId, int[] v, TimeTag tt) {
             if (v!=null)
               by = v[0];
           }};
@@ -183,8 +180,8 @@ class Circle
     fill(0, 0, 256);
     text(""+id, bx, by);
 
-    sig_x_out.update_instance(id, (int)bx);
-    sig_y_out.update_instance(id, (int)by);
+    sig_x_out.updateInstance(id, (int)bx);
+    sig_y_out.updateInstance(id, (int)by);
   }
 }
 
diff --git a/jni/mapperjni.c b/jni/mapperjni.c
index 3581c02..f704337 100644
--- a/jni/mapperjni.c
+++ b/jni/mapperjni.c
@@ -63,6 +63,15 @@ static void throwIllegalArgumentSignal(JNIEnv *env)
     }
 }
 
+static void throwIllegalArgument(JNIEnv *env, const char *message)
+{
+    jclass newExcCls =
+    (*env)->FindClass(env, "java/lang/IllegalArgumentException");
+    if (newExcCls) {
+        (*env)->ThrowNew(env, newExcCls, message);
+    }
+}
+
 static void throwOutOfMemory(JNIEnv *env)
 {
     jclass newExcCls =
@@ -102,6 +111,21 @@ static mapper_signal get_signal_from_jobject(JNIEnv *env, jobject obj)
     return 0;
 }
 
+static mapper_db get_db_from_jobject(JNIEnv *env, jobject obj)
+{
+    // TODO check device here
+    jclass cls = (*env)->GetObjectClass(env, obj);
+    if (cls) {
+        jfieldID val = (*env)->GetFieldID(env, cls, "_db", "J");
+        if (val) {
+            jlong s = (*env)->GetLongField(env, obj, val);
+            return (mapper_db)ptr_jlong(s);
+        }
+    }
+    throwIllegalArgument(env, "Couldn't retrieve db pointer.");
+    return 0;
+}
+
 static mapper_timetag_t *get_timetag_from_jobject(JNIEnv *env, jobject obj,
                                                   mapper_timetag_t *tt)
 {
@@ -140,6 +164,34 @@ static jobject get_jobject_from_timetag(JNIEnv *env, mapper_timetag_t *tt)
     return objtt;
 }
 
+static mapper_db_device* get_db_device_ptr_from_jobject(JNIEnv *env, jobject obj)
+{
+    jclass cls = (*env)->GetObjectClass(env, obj);
+    if (cls) {
+        jfieldID val = (*env)->GetFieldID(env, cls, "_devprops_p", "J");
+        if (val) {
+            jlong s = (*env)->GetLongField(env, obj, val);
+            return (mapper_db_device*)ptr_jlong(s);
+        }
+    }
+    throwIllegalArgument(env, "Couldn't retrieve mapper_db_device* ptr.");
+    return 0;
+}
+
+static mapper_db_signal* get_db_signal_ptr_from_jobject(JNIEnv *env, jobject obj)
+{
+    jclass cls = (*env)->GetObjectClass(env, obj);
+    if (cls) {
+        jfieldID val = (*env)->GetFieldID(env, cls, "_sigprops_p", "J");
+        if (val) {
+            jlong s = (*env)->GetLongField(env, obj, val);
+            return (mapper_db_signal*)ptr_jlong(s);
+        }
+    }
+    throwIllegalArgument(env, "Couldn't retrieve mapper_db_signal* ptr.");
+    return 0;
+}
+
 static jobject build_PropertyValue(JNIEnv *env, const char type,
                                    const void *value, const int length)
 {
@@ -165,7 +217,6 @@ static jobject build_PropertyValue(JNIEnv *env, const char type,
                     return (*env)->NewObject(env, cls, methodID, type, arr);
                 }
             }
-                
             break;
         }
         case 'f': {
@@ -236,15 +287,130 @@ static jobject build_PropertyValue(JNIEnv *env, const char type,
     return 0;
 }
 
+static int get_PropertyValue_elements(JNIEnv *env, jobject jprop, void **value,
+                                      char *type)
+{
+    jclass cls = (*env)->GetObjectClass(env, jprop);
+    if (!cls)
+        return 0;
+
+    jfieldID typeid = (*env)->GetFieldID(env, cls, "type", "C");
+    jfieldID lengthid = (*env)->GetFieldID(env, cls, "length", "I");
+    if (!typeid || !lengthid)
+        return 0;
+
+    *type = (*env)->GetCharField(env, jprop, typeid);
+    int length = (*env)->GetIntField(env, jprop, lengthid);
+    if (!length)
+        return 0;
+
+    jfieldID valf = 0;
+    jobject o = 0;
+
+    switch (*type)
+    {
+        case 'i':
+            valf = (*env)->GetFieldID(env, cls, "_i", "[I");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            *value = (*env)->GetIntArrayElements(env, o, NULL);
+            break;
+        case 'f':
+            valf = (*env)->GetFieldID(env, cls, "_f", "[F");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            *value = (*env)->GetFloatArrayElements(env, o, NULL);
+            break;
+        case 'd':
+            valf = (*env)->GetFieldID(env, cls, "_d", "[D");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            *value = (*env)->GetDoubleArrayElements(env, o, NULL);
+            break;
+        case 's':
+        case 'S':
+        {
+            valf = (*env)->GetFieldID(env, cls, "_s", "[Ljava/lang/String;");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            // need to unpack string array and rebuild
+            jstring jstrings[length];
+            const char **cstrings = malloc(sizeof(char*) * length);
+            int i;
+            for (i = 0; i < length; i++) {
+                jstrings[i] = (jstring) (*env)->GetObjectArrayElement(env, o, i);
+                cstrings[i] = (*env)->GetStringUTFChars(env, jstrings[i], 0);
+            }
+            *value = cstrings;
+            break;
+        }
+        default:
+            return 0;
+    }
+    return length;
+}
+
+static void release_PropertyValue_elements(JNIEnv *env, jobject jprop,
+                                           void *value)
+{
+    jclass cls = (*env)->GetObjectClass(env, jprop);
+    if (!cls)
+        return;
+
+    jfieldID typeid = (*env)->GetFieldID(env, cls, "type", "C");
+    jfieldID lengthid = (*env)->GetFieldID(env, cls, "length", "I");
+    if (!typeid || !lengthid)
+        return;
+
+    char type = (*env)->GetCharField(env, jprop, typeid);
+    int length = (*env)->GetIntField(env, jprop, lengthid);
+    if (!length)
+        return;
+
+    jfieldID valf = 0;
+    jobject o = 0;
+
+    switch (type)
+    {
+        case 'i':
+            valf = (*env)->GetFieldID(env, cls, "_i", "[I");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            (*env)->ReleaseIntArrayElements(env, o, value, JNI_ABORT);
+            break;
+        case 'f':
+            valf = (*env)->GetFieldID(env, cls, "_f", "[F");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            (*env)->ReleaseFloatArrayElements(env, o, value, JNI_ABORT);
+            break;
+        case 'd':
+            valf = (*env)->GetFieldID(env, cls, "_d", "[D");
+            o = (*env)->GetObjectField(env, jprop, valf);
+            (*env)->ReleaseDoubleArrayElements(env, o, value, JNI_ABORT);
+            break;
+        case 's':
+        case 'S':
+        {
+            valf = (*env)->GetFieldID(env, cls, "_s", "[Ljava/lang/String;");
+            o = (*env)->GetObjectField(env, jprop, valf);
+
+            jstring jstr;
+            const char **cstrings = (const char**)value;
+            int i;
+            for (i = 0; i < length; i++) {
+                jstr = (jstring) (*env)->GetObjectArrayElement(env, o, i);
+                (*env)->ReleaseStringUTFChars(env, jstr, cstrings[i]);
+            }
+            free(cstrings);
+            break;
+        }
+    }
+}
+
 /**** Mapper.Device ****/
 
 JNIEXPORT jlong JNICALL Java_Mapper_Device_mdev_1new
   (JNIEnv *env, jobject obj, jstring name, jint port)
 {
     const char *cname = (*env)->GetStringUTFChars(env, name, 0);
-    mapper_device d = mdev_new(cname, port, 0);
+    mapper_device dev = mdev_new(cname, port, 0);
     (*env)->ReleaseStringUTFChars(env, name, cname);
-    return jlong_ptr(d);
+    return jlong_ptr(dev);
 }
 
 JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1free
@@ -334,32 +500,28 @@ static void java_msig_input_cb(mapper_signal sig, mapper_db_signal props,
     if (instance_id != 0)
         input_cb = (jobject)msig_get_instance_data(sig, instance_id);
 
-    if (input_cb && ctx->signal && ctx->db_signal) {
+    if (input_cb && ctx->signal) {
         jclass cls = (*genv)->GetObjectClass(genv, input_cb);
         if (cls) {
             jmethodID mid=0;
             if (props->type=='i')
                 mid = (*genv)->GetMethodID(genv, cls, "onInput",
                                            "(LMapper/Device$Signal;"
-                                           "LMapper/Db/Signal;"
                                            "I[I"
                                            "LMapper/TimeTag;)V");
             else if (props->type=='f')
                 mid = (*genv)->GetMethodID(genv, cls, "onInput",
                                            "(LMapper/Device$Signal;"
-                                           "LMapper/Db/Signal;"
                                            "I[F"
                                            "LMapper/TimeTag;)V");
             else if (props->type=='d')
                 mid = (*genv)->GetMethodID(genv, cls, "onInput",
                                            "(LMapper/Device$Signal;"
-                                           "LMapper/Db/Signal;"
                                            "I[D"
                                            "LMapper/TimeTag;)V");
 
             if (mid) {
-                (*genv)->CallVoidMethod(genv, input_cb, mid,
-                                        ctx->signal, ctx->db_signal,
+                (*genv)->CallVoidMethod(genv, input_cb, mid, ctx->signal,
                                         instance_id, vobj, objtt);
                 if ((*genv)->ExceptionOccurred(genv))
                     bailing = 1;
@@ -377,11 +539,11 @@ static void java_msig_input_cb(mapper_signal sig, mapper_db_signal props,
         (*genv)->DeleteLocalRef(genv, objtt);
 }
 
-static void msig_instance_event_cb(mapper_signal sig,
-                                   mapper_db_signal props,
-                                   int instance_id,
-                                   msig_instance_event_t event,
-                                   mapper_timetag_t *tt)
+static void java_msig_instance_event_cb(mapper_signal sig,
+                                        mapper_db_signal props,
+                                        int instance_id,
+                                        msig_instance_event_t event,
+                                        mapper_timetag_t *tt)
 {
     if (bailing)
         return;
@@ -389,19 +551,17 @@ static void msig_instance_event_cb(mapper_signal sig,
     jobject objtt = get_jobject_from_timetag(genv, tt);
 
     msig_jni_context ctx = (msig_jni_context)props->user_data;
-    if (ctx->instanceHandler && ctx->signal && ctx->db_signal) {
+    if (ctx->instanceHandler && ctx->signal) {
         jclass cls = (*genv)->GetObjectClass(genv, ctx->instanceHandler);
         if (cls) {
             jmethodID mid=0;
             mid = (*genv)->GetMethodID(genv, cls, "onEvent",
-                                       "(LMapper/Device$Signal;"
-                                       "LMapper/Db/Signal;II"
+                                       "(LMapper/Device$Signal;II"
                                        "LMapper/TimeTag;)V");
 
             if (mid) {
                 (*genv)->CallVoidMethod(genv, ctx->instanceHandler, mid,
-                                        ctx->signal, ctx->db_signal,
-                                        instance_id, event, objtt);
+                                        ctx->signal, instance_id, event, objtt);
                 if ((*genv)->ExceptionOccurred(genv))
                     bailing = 1;
             }
@@ -423,7 +583,7 @@ static jobject create_signal_object(JNIEnv *env, jobject devobj,
 {
     jobject sigobj = 0, sigdbobj = 0;
     // Create a wrapper class for this signal
-    jclass cls = (*env)->FindClass(env, "LMapper/Device$Signal;");
+    jclass cls = (*env)->FindClass(env, "Mapper/Device$Signal");
     if (cls) {
         jmethodID mid = (*env)->GetMethodID(env, cls, "<init>",
                             "(LMapper/Device;JLMapper/Device;)V");
@@ -439,13 +599,11 @@ static jobject create_signal_object(JNIEnv *env, jobject devobj,
         ctx->db_signal = (*env)->NewGlobalRef(env, sigdbobj);
 
         // Create a wrapper class for this signal's properties
-        cls = (*env)->FindClass(env, "LMapper/Db/Signal;");
+        cls = (*env)->FindClass(env, "Mapper/Db/Signal");
         if (cls) {
-            jmethodID mid = (*env)->GetMethodID(env, cls, "<init>",
-                                       "(JLMapper/Device$Signal;)V");
+            jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
             sigdbobj = (*env)->NewObject(env, cls, mid,
-                                         jlong_ptr(msig_properties(s)),
-                                         sigobj);
+                                         jlong_ptr(msig_properties(s)));
         }
     }
 
@@ -464,7 +622,7 @@ static jobject create_signal_object(JNIEnv *env, jobject devobj,
     return sigobj;
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Device_add_1input
+JNIEXPORT jobject JNICALL Java_Mapper_Device_addInput
   (JNIEnv *env, jobject obj, jstring name, jint length, jchar type,
    jstring unit, jobject minimum, jobject maximum, jobject listener)
 {
@@ -504,7 +662,7 @@ JNIEXPORT jobject JNICALL Java_Mapper_Device_add_1input
     return create_signal_object(env, obj, ctx, listener, s);
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Device_add_1output
+JNIEXPORT jobject JNICALL Java_Mapper_Device_addOutput
   (JNIEnv *env, jobject obj, jstring name, jint length, jchar type,
    jstring unit, jobject minimum, jobject maximum)
 {
@@ -659,6 +817,23 @@ JNIEXPORT jlong JNICALL Java_Mapper_Device_mdev_1get_1output_1by_1index
     return jlong_ptr(sig);
 }
 
+JNIEXPORT jobject JNICALL Java_Mapper_Device_properties
+  (JNIEnv *env, jobject obj)
+{
+    mapper_device dev = get_device_from_jobject(env, obj);
+    if (!dev) return 0;
+
+    // Create a wrapper class for this device's properties
+    mapper_db_device props = mdev_properties(dev);
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/Device");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject devdbobj = (*env)->NewObject(env, cls, mid,
+                                         jlong_ptr(props));
+    return devdbobj;
+}
+
 JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1set_1property
   (JNIEnv *env, jobject obj, jlong d, jstring key, jobject value)
 {
@@ -695,7 +870,7 @@ JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1set_1property
                 case 'd':
                     valf = (*env)->GetFieldID(env, cls, "_d", "[D");
                     o = (*env)->GetObjectField(env, value, valf);
-                    propval = (*env)->GetDoubleArrayElements(env, value, NULL);
+                    propval = (*env)->GetDoubleArrayElements(env, o, NULL);
                     mdev_set_property(dev, ckey, type, propval, length);
                     (*env)->ReleaseDoubleArrayElements(env, o, propval, JNI_ABORT);
                     break;
@@ -724,8 +899,6 @@ JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1set_1property
     (*env)->ReleaseStringUTFChars(env, key, ckey);
 }
 
-// TODO: add generic device properties functions
-
 JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1remove_1property
   (JNIEnv *env, jobject obj, jlong d, jstring key)
 {
@@ -815,6 +988,16 @@ JNIEXPORT void JNICALL Java_Mapper_Device_mdev_1send_1queue
         mdev_send_queue(dev, *ptt);
 }
 
+JNIEXPORT jobject JNICALL Java_Mapper_Device_mdev_1now
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_device dev = (mapper_device)ptr_jlong(p);
+    mapper_timetag_t tt;
+    mdev_now(dev, &tt);
+    jobject o = get_jobject_from_timetag(genv, &tt);
+    return o;
+}
+
 /**** Mapper.Device.Signal ****/
 
 JNIEXPORT jstring JNICALL Java_Mapper_Device_00024Signal_msig_1full_1name
@@ -863,7 +1046,7 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_msig_1set_1rate
         msig_set_rate(sig, (float)rate);
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_query_1remotes
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_queryRemotes
   (JNIEnv *env, jobject obj, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
@@ -962,8 +1145,8 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_msig_1remove_1property
     (*env)->ReleaseStringUTFChars(env, key, ckey);
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__ILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__IILMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jint value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -978,19 +1161,19 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__ILMapper_TimeTag_2
     ptt = get_timetag_from_jobject(env, objtt, &tt);
 
     if (props->type == 'i')
-        msig_update(sig, &value, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
     else if (props->type == 'f') {
         float v = (float)value;
-        msig_update(sig, &v, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
     }
     else if (props->type == 'd') {
         double v = (double)value;
-        msig_update(sig, &v, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__FLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jfloat value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__IFLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jfloat value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -1007,16 +1190,16 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__FLMapper_TimeTag_2
     if (props->type == 'i')
         throwIllegalArgumentTruncate(env, sig);
     else if (props->type == 'f') {
-        msig_update(sig, &value, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
     }
     else if (props->type == 'd') {
         double v = (double)value;
-        msig_update(sig, &v, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__DLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jdouble value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__IDLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jdouble value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -1034,15 +1217,15 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__DLMapper_TimeTag_2
         throwIllegalArgumentTruncate(env, sig);
     else if (props->type == 'f') {
         float v = (float)value;
-        msig_update(sig, &v, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
     }
     else if (props->type == 'd') {
-        msig_update(sig, &value, 1, ptt ? *ptt : MAPPER_NOW);
+        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3ILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jintArray value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__I_3ILMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jintArray value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -1067,7 +1250,8 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3ILMapper_TimeTag
             int i;
             for (i=0; i<length; i++)
                 arraycopy[i] = (float)array[i];
-            msig_update(sig, arraycopy, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, arraycopy, 0,
+                                 ptt ? *ptt : MAPPER_NOW);
             free(arraycopy);
         }
         else if (props->type == 'd') {
@@ -1075,15 +1259,16 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3ILMapper_TimeTag
             int i;
             for (i=0; i<length; i++)
                 arraycopy[i] = (double)array[i];
-            msig_update(sig, arraycopy, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, arraycopy, 0,
+                                 ptt ? *ptt : MAPPER_NOW);
             free(arraycopy);
         }
         (*env)->ReleaseIntArrayElements(env, value, array, JNI_ABORT);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3FLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jfloatArray value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__I_3FLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jfloatArray value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -1105,22 +1290,23 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3FLMapper_TimeTag
     jfloat *array = (*env)->GetFloatArrayElements(env, value, 0);
     if (array) {
         if (props->type == 'f') {
-            msig_update(sig, array, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, array, 0, ptt ? *ptt : MAPPER_NOW);
         }
         else {
             double *arraycopy = malloc(sizeof(double)*length);
             int i;
             for (i=0; i<length; i++)
                 arraycopy[i] = (double)array[i];
-            msig_update(sig, arraycopy, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, arraycopy, 0,
+                                 ptt ? *ptt : MAPPER_NOW);
             free(arraycopy);
         }
         (*env)->ReleaseFloatArrayElements(env, value, array, JNI_ABORT);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3DLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jdoubleArray value, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_updateInstance__I_3DLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jdoubleArray value, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
     if (!sig) return;
@@ -1142,357 +1328,187 @@ JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3DLMapper_TimeTag
     jdouble *array = (*env)->GetDoubleArrayElements(env, value, 0);
     if (array) {
         if (props->type == 'd') {
-            msig_update(sig, array, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, array, 0, ptt ? *ptt : MAPPER_NOW);
         }
-        else {
+        else if (props->type == 'f') {
             float *arraycopy = malloc(sizeof(float)*length);
             int i;
             for (i=0; i<length; i++)
                 arraycopy[i] = (float)array[i];
-            msig_update(sig, arraycopy, 0, ptt ? *ptt : MAPPER_NOW);
+            msig_update_instance(sig, id, arraycopy, 0,
+                                 ptt ? *ptt : MAPPER_NOW);
             free(arraycopy);
         }
         (*env)->ReleaseDoubleArrayElements(env, value, array, JNI_ABORT);
     }
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__I
-  (JNIEnv *env, jobject obj, jint value)
+JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instanceValue__I_3ILMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jintArray ar, jobject objtt)
 {
-    Java_Mapper_Device_00024Signal_update__ILMapper_TimeTag_2
-        (env, obj, value, NULL);
-}
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return JNI_FALSE;
+    mapper_db_signal props = msig_properties(sig);
+    mapper_timetag_t tt;
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__F
-  (JNIEnv *env, jobject obj, jfloat value)
-{
-    Java_Mapper_Device_00024Signal_update__FLMapper_TimeTag_2
-        (env, obj, value, NULL);
-}
+    int length = (*env)->GetArrayLength(env, ar);
+    if (length < props->length) {
+        throwIllegalArgumentLength(env, sig, length);
+        return JNI_FALSE;
+    }
+    if (props->type != 'i') {
+        throwIllegalArgumentTruncate(env, sig);
+        return JNI_FALSE;
+    }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update__D
-  (JNIEnv *env, jobject obj, jdouble value)
-{
-    Java_Mapper_Device_00024Signal_update__DLMapper_TimeTag_2
-        (env, obj, value, NULL);
-}
+    int *value = msig_instance_value(sig, id, &tt);
+    if (!value)
+        return JNI_FALSE;
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3I
-  (JNIEnv *env, jobject obj, jintArray value)
-{
-    Java_Mapper_Device_00024Signal_update___3ILMapper_TimeTag_2
-        (env, obj, value, NULL);
-}
+    jint *array = (*env)->GetIntArrayElements(env, ar, 0);
+    if (array) {
+        int i;
+        for (i=0; i < props->length; i++)
+            array[i] = (jint)value[i];
+        (*env)->ReleaseIntArrayElements(env, ar, array, 0);
+    }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3F
-  (JNIEnv *env, jobject obj, jfloatArray value)
-{
-    Java_Mapper_Device_00024Signal_update___3FLMapper_TimeTag_2
-        (env, obj, value, NULL);
-}
+    if (objtt) {
+        jclass cls = (*env)->GetObjectClass(env, objtt);
+        if (cls) {
+            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
+            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
+            if (sec && frac) {
+                (*env)->SetLongField(env, objtt, sec, tt.sec);
+                (*env)->SetLongField(env, objtt, frac, tt.frac);
+            }
+        }
+    }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update___3D
-  (JNIEnv *env, jobject obj, jdoubleArray value)
-{
-    Java_Mapper_Device_00024Signal_update___3DLMapper_TimeTag_2
-        (env, obj, value, NULL);
+    return JNI_TRUE;
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__IILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jint value, jobject objtt)
+JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instanceValue__I_3FLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jfloatArray ar, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
+    if (!sig) return JNI_FALSE;
     mapper_db_signal props = msig_properties(sig);
-    if (props->length != 1) {
-        throwIllegalArgumentLength(env, sig, 1);
-        return;
+    mapper_timetag_t tt;
+
+    int length = (*env)->GetArrayLength(env, ar);
+    if (length < props->length) {
+        throwIllegalArgumentLength(env, sig, length);
+        return JNI_FALSE;
     }
 
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
+    jfloat *array = (*env)->GetFloatArrayElements(env, ar, 0);
+    if (array)
+    {
+        int i;
+        switch (props->type)
+        {
+            case 'i': {
+                int *value = msig_instance_value(sig, id, &tt);
+                if (!value) {
+                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    return JNI_FALSE;
+                }
+                for (i=0; i < props->length; i++)
+                    array[i] = (jfloat)value[i];
+            } break;
 
-    if (props->type == 'i')
-        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
-    else if (props->type == 'f') {
-        float v = (float)value;
-        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
+            case 'f': {
+                float *value = msig_instance_value(sig, id, &tt);
+                if (!value) {
+                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    return JNI_FALSE;
+                }
+                for (i=0; i < props->length; i++)
+                    array[i] = (jfloat)value[i];
+            } break;
+
+            case 'd': {
+                double *value = msig_instance_value(sig, id, &tt);
+                if (!value) {
+                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    return JNI_FALSE;
+                }
+                for (i=0; i < props->length; i++)
+                    array[i] = (jfloat)value[i];
+            } break;
+        }
+
+        (*env)->ReleaseFloatArrayElements(env, ar, array, 0);
     }
-    else if (props->type == 'd') {
-        double v = (double)value;
-        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
+
+    if (objtt) {
+        jclass cls = (*env)->GetObjectClass(env, objtt);
+        if (cls) {
+            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
+            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
+            if (sec && frac) {
+                (*env)->SetLongField(env, objtt, sec, tt.sec);
+                (*env)->SetLongField(env, objtt, frac, tt.frac);
+            }
+        }
     }
+
+    return JNI_TRUE;
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__IFLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jfloat value, jobject objtt)
+JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instanceValue__I_3DLMapper_TimeTag_2
+  (JNIEnv *env, jobject obj, jint id, jdoubleArray ar, jobject objtt)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
+    if (!sig) return JNI_FALSE;
     mapper_db_signal props = msig_properties(sig);
-    if (props->length != 1) {
-        throwIllegalArgumentLength(env, sig, 1);
-        return;
+    mapper_timetag_t tt;
+
+    int length = (*env)->GetArrayLength(env, ar);
+    if (length < props->length) {
+        throwIllegalArgumentLength(env, sig, length);
+        return JNI_FALSE;
     }
 
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-
-    if (props->type == 'i')
-        throwIllegalArgumentTruncate(env, sig);
-    else if (props->type == 'f') {
-        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
-    }
-    else if (props->type == 'd') {
-        double v = (double)value;
-        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
-    }
-}
-
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__IDLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jdouble value, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
-    mapper_db_signal props = msig_properties(sig);
-    if (props->length != 1) {
-        throwIllegalArgumentLength(env, sig, 1);
-        return;
-    }
-
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-
-    if (props->type == 'i')
-        throwIllegalArgumentTruncate(env, sig);
-    else if (props->type == 'f') {
-        float v = (float)value;
-        msig_update_instance(sig, id, &v, 1, ptt ? *ptt : MAPPER_NOW);
-    }
-    else if (props->type == 'd') {
-        msig_update_instance(sig, id, &value, 1, ptt ? *ptt : MAPPER_NOW);
-    }
-}
-
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__I_3ILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jintArray value, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
-    mapper_db_signal props = msig_properties(sig);
-    int length = (*env)->GetArrayLength(env, value);
-    if (length != props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return;
-    }
-
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-
-    jint *array = (*env)->GetIntArrayElements(env, value, 0);
-    if (array) {
-        if (props->type == 'i') {
-            msig_update(sig, array, 0, MAPPER_NOW);
-        }
-        else if (props->type == 'f') {
-            float *arraycopy = malloc(sizeof(float)*length);
-            int i;
-            for (i=0; i<length; i++)
-                arraycopy[i] = (float)array[i];
-            msig_update_instance(sig, id, arraycopy, 0,
-                                 ptt ? *ptt : MAPPER_NOW);
-            free(arraycopy);
-        }
-        else if (props->type == 'd') {
-            double *arraycopy = malloc(sizeof(double)*length);
-            int i;
-            for (i=0; i<length; i++)
-                arraycopy[i] = (double)array[i];
-            msig_update_instance(sig, id, arraycopy, 0,
-                                 ptt ? *ptt : MAPPER_NOW);
-            free(arraycopy);
-        }
-        (*env)->ReleaseIntArrayElements(env, value, array, JNI_ABORT);
-    }
-}
-
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__I_3FLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jfloatArray value, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
-    mapper_db_signal props = msig_properties(sig);
-    int length = (*env)->GetArrayLength(env, value);
-    if (length != props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return;
-    }
-    if (props->type == 'i') {
-        throwIllegalArgumentTruncate(env, sig);
-        return;
-    }
-
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-
-    jfloat *array = (*env)->GetFloatArrayElements(env, value, 0);
-    if (array) {
-        if (props->type == 'f') {
-            msig_update_instance(sig, id, array, 0, ptt ? *ptt : MAPPER_NOW);
-        }
-        else {
-            double *arraycopy = malloc(sizeof(double)*length);
-            int i;
-            for (i=0; i<length; i++)
-                arraycopy[i] = (double)array[i];
-            msig_update_instance(sig, id, arraycopy, 0,
-                                 ptt ? *ptt : MAPPER_NOW);
-            free(arraycopy);
-        }
-        (*env)->ReleaseFloatArrayElements(env, value, array, JNI_ABORT);
-    }
-}
-
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_update_1instance__I_3DLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jdoubleArray value, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
-    mapper_db_signal props = msig_properties(sig);
-    int length = (*env)->GetArrayLength(env, value);
-    if (length != props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return;
-    }
-    if (props->type == 'i') {
-        throwIllegalArgumentTruncate(env, sig);
-        return;
-    }
-
-    mapper_timetag_t tt, *ptt;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-
-    jdouble *array = (*env)->GetDoubleArrayElements(env, value, 0);
-    if (array) {
-        if (props->type == 'd') {
-            msig_update_instance(sig, id, array, 0, ptt ? *ptt : MAPPER_NOW);
-        }
-        else if (props->type == 'f') {
-            float *arraycopy = malloc(sizeof(float)*length);
-            int i;
-            for (i=0; i<length; i++)
-                arraycopy[i] = (float)array[i];
-            msig_update_instance(sig, id, arraycopy, 0,
-                                 ptt ? *ptt : MAPPER_NOW);
-            free(arraycopy);
-        }
-        (*env)->ReleaseDoubleArrayElements(env, value, array, JNI_ABORT);
-    }
-}
-
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_value___3ILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jintArray ar, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
-    mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
-
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
-    }
-    if (props->type != 'i') {
-        throwIllegalArgumentTruncate(env, sig);
-        return JNI_FALSE;
-    }
-
-    int *value = msig_value(sig, &tt);
-    if (!value)
-        return JNI_FALSE;
-
-    jint *array = (*env)->GetIntArrayElements(env, ar, 0);
-    if (array) {
-        int i;
-        for (i=0; i < props->length; i++)
-            array[i] = (jint)value[i];
-        (*env)->ReleaseIntArrayElements(env, ar, array, 0);
-    }
-
-    if (objtt) {
-        jclass cls = (*env)->GetObjectClass(env, objtt);
-        if (cls) {
-            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
-            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
-            if (sec && frac) {
-                (*env)->SetLongField(env, objtt, sec, tt.sec);
-                (*env)->SetLongField(env, objtt, frac, tt.frac);
-            }
-        }
-    }
-
-    return JNI_TRUE;
-}
-
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_value___3FLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jfloatArray ar, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
-    mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
-
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
-    }
-
-    jfloat *array = (*env)->GetFloatArrayElements(env, ar, 0);
+    jdouble *array = (*env)->GetDoubleArrayElements(env, ar, 0);
     if (array)
     {
         int i;
         switch (props->type)
         {
             case 'i': {
-                int *value = msig_value(sig, &tt);
+                int *value = msig_instance_value(sig, id, &tt);
                 if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
                     return JNI_FALSE;
                 }
                 for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
+                    array[i] = (jdouble)value[i];
             } break;
 
             case 'f': {
-                float *value = msig_value(sig, &tt);
+                float *value = msig_instance_value(sig, id, &tt);
                 if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
                     return JNI_FALSE;
                 }
                 for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
+                    array[i] = (jdouble)value[i];
             } break;
 
             case 'd': {
-                double *value = msig_value(sig, &tt);
+                double *value = msig_instance_value(sig, id, &tt);
                 if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
+                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
                     return JNI_FALSE;
                 }
                 for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
+                    array[i] = (jdouble)value[i];
             } break;
         }
 
-        (*env)->ReleaseFloatArrayElements(env, ar, array, 0);
+        (*env)->ReleaseDoubleArrayElements(env, ar, array, 0);
     }
 
     if (objtt) {
@@ -1510,610 +1526,1616 @@ JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_value___3FLMapper_Time
     return JNI_TRUE;
 }
 
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_value___3DLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jdoubleArray ar, jobject objtt)
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_setInstanceEventCallback
+  (JNIEnv *env, jobject obj, jobject handler, jint flags)
 {
     mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
+    if (!sig) return;
+
     mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
+    msig_jni_context ctx = (msig_jni_context)props->user_data;
 
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
+    if (ctx->instanceHandler)
+        (*env)->DeleteGlobalRef(env, ctx->instanceHandler);
+    if (handler) {
+        ctx->instanceHandler = (*env)->NewGlobalRef(env, handler);
+        msig_set_instance_event_callback(sig,
+                                         java_msig_instance_event_cb,
+                                         flags, ctx);
+    }
+    else {
+        ctx->instanceHandler = 0;
+        msig_set_instance_event_callback(sig, 0, flags, 0);
     }
+}
 
-    jdouble *array = (*env)->GetDoubleArrayElements(env, ar, 0);
-    if (array)
-    {
-        int i;
-        switch (props->type)
-        {
-            case 'i': {
-                int *value = msig_value(sig, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_setCallback
+  (JNIEnv *env, jobject obj, jobject handler)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return;
+
+    mapper_db_signal props = msig_properties(sig);
+    msig_jni_context ctx = (msig_jni_context)props->user_data;
+    if (ctx->listener)
+        (*env)->DeleteGlobalRef(env, ctx->listener);
+    if (handler) {
+        ctx->listener = (*env)->NewGlobalRef(env, handler);
+        msig_set_callback(sig, java_msig_input_cb, ctx);
+    }
+    else {
+        ctx->listener = 0;
+        msig_set_callback(sig, 0, 0);
+    }
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_setInstanceCallback
+  (JNIEnv *env, jobject obj, jint instance_id, jobject data)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig)
+        return;
+    jobject prev = (jobject)msig_get_instance_data(sig, instance_id);
+    if (prev)
+        (*env)->DeleteGlobalRef(env, prev);
+
+    // Note that msig_set_instance_data() can trigger the instance
+    // event callback, so we need to use the global to pass the
+    // environment to the handler.
+    genv = env;
+    bailing = 0;
+
+    msig_set_instance_data(sig, instance_id,
+                           data ? (*env)->NewGlobalRef(env, data) : 0);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_getInstanceCallback
+  (JNIEnv *env, jobject obj, jint instance_id)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig)
+        return 0;
+    return (jobject)msig_get_instance_data(sig, instance_id);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_reserveInstances
+  (JNIEnv *env, jobject obj, jintArray ids, jint num, jobject cb)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig)
+        return 0;
+    int i, reserved = 0;
+    jobject ref = cb ? (*env)->NewGlobalRef(env, cb) : 0;
+    if (ids) {
+        int length = (*env)->GetArrayLength(env, ids);
+        jint *array = (*env)->GetIntArrayElements(env, ids, 0);
+        for (i = 0; i < length; i++) {
+            int id = (int)array[i];
+            reserved += msig_reserve_instances(sig, 1, &id, ref ? (void **)&ref : 0);
+        }
+        (*env)->ReleaseIntArrayElements(env, ids, array, JNI_ABORT);
+        return reserved;
+    }
+    else {
+        for (i = 0; i < num; i++) {
+            reserved += msig_reserve_instances(sig, 1, 0, (void **)&ref);
+        }
+        return reserved;
+    }
+    return 0;
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_releaseInstance
+  (JNIEnv *env, jobject obj, jint instance_id, jobject objtt)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return;
+    mapper_timetag_t tt, *ptt=0;
+    ptt = get_timetag_from_jobject(env, objtt, &tt);
+    msig_release_instance(sig, instance_id, ptt ? *ptt : MAPPER_NOW);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_removeInstance
+  (JNIEnv *env, jobject obj, jint instance_id)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return;
+    msig_remove_instance(sig, instance_id);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_oldestActiveInstance
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    int i, r = msig_get_oldest_active_instance(sig, &i);
+    jobject iobj = 0;
+    if (r == 0) {
+        jclass cls = (*env)->FindClass(env, "java/lang/Integer");
+        if (cls) {
+            jmethodID cons = (*env)->GetMethodID(env, cls,
+                                                 "<init>", "(I)V");
+            if (cons) {
+                iobj = (*env)->NewObject(env, cls, cons, i);
+                return iobj;
+            }
+        }
+    }
+    return 0;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_newestActiveInstance
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    int i, r = msig_get_newest_active_instance(sig, &i);
+    jobject iobj = 0;
+    if (r == 0) {
+        jclass cls = (*env)->FindClass(env, "java/lang/Integer");
+        if (cls) {
+            jmethodID cons = (*env)->GetMethodID(env, cls,
+                                                 "<init>", "(I)V");
+            if (cons) {
+                iobj = (*env)->NewObject(env, cls, cons, i);
+                return iobj;
+            }
+        }
+    }
+    return 0;
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_match_1instances
+  (JNIEnv *env, jobject obj, jobject from, jobject to, jint instance_id)
+{
+    mapper_signal sigfrom = get_signal_from_jobject(env, from);
+    mapper_signal sigto = get_signal_from_jobject(env, to);
+    if (!sigto || !sigfrom) return;
+
+    msig_match_instances(sigfrom, sigto, instance_id);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_numActiveInstances
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    return msig_num_active_instances(sig);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_numReservedInstances
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    return msig_num_reserved_instances(sig);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_activeInstanceId
+  (JNIEnv *env, jobject obj, jint index)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    return msig_active_instance_id(sig, index);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_setInstanceAllocationMode
+  (JNIEnv *env, jobject obj, jint mode)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return;
+    msig_set_instance_allocation_mode(sig, mode);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_instanceAllocationMode
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    return msig_get_instance_allocation_mode(sig);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_numConnections
+  (JNIEnv *env, jobject obj)
+{
+    mapper_signal sig = get_signal_from_jobject(env, obj);
+    if (!sig) return 0;
+    return msig_num_connections(sig);
+}
+
+/**** Mapper.Monitor ****/
+
+JNIEXPORT jlong JNICALL Java_Mapper_Monitor_mmon_1new
+  (JNIEnv *env, jobject obj, jint autosubscribe_flags)
+{
+    mapper_monitor mon = mapper_monitor_new(0, autosubscribe_flags);
+    return jlong_ptr(mon);
+}
+
+JNIEXPORT jlong JNICALL Java_Mapper_Monitor_mmon_1get_1db
+(JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    mapper_db db = mapper_monitor_get_db(mon);
+    return jlong_ptr(db);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1free
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    mapper_monitor_free(mon);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Monitor_mmon_1poll
+  (JNIEnv *env, jobject obj, jlong d, jint timeout)
+{
+    genv = env;
+    bailing = 0;
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(d);
+    return mapper_monitor_poll(mon, timeout);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1subscribe
+  (JNIEnv *env, jobject obj, jlong p, jstring name, jint flags, jint timeout)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *cname = (*env)->GetStringUTFChars(env, name, 0);
+    mapper_monitor_subscribe(mon, cname, flags, timeout);
+    (*env)->ReleaseStringUTFChars(env, name, cname);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1unsubscribe
+  (JNIEnv *env, jobject obj, jlong p, jstring name)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *cname = (*env)->GetStringUTFChars(env, name, 0);
+    mapper_monitor_unsubscribe(mon, cname);
+    (*env)->ReleaseStringUTFChars(env, name, cname);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1link
+  (JNIEnv *env, jobject obj, jlong p, jstring src_name, jstring dest_name,
+   jobject link_props)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *csrc_name = (*env)->GetStringUTFChars(env, src_name, 0);
+    const char *cdest_name = (*env)->GetStringUTFChars(env, dest_name, 0);
+// TODO: process props!
+    mapper_monitor_link(mon, csrc_name, cdest_name, 0, 0);
+    (*env)->ReleaseStringUTFChars(env, src_name, csrc_name);
+    (*env)->ReleaseStringUTFChars(env, dest_name, cdest_name);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1unlink
+  (JNIEnv *env, jobject obj, jlong p, jstring src_name, jstring dest_name)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *csrc_name = (*env)->GetStringUTFChars(env, src_name, 0);
+    const char *cdest_name = (*env)->GetStringUTFChars(env, dest_name, 0);
+    mapper_monitor_unlink(mon, csrc_name, cdest_name);
+    (*env)->ReleaseStringUTFChars(env, src_name, csrc_name);
+    (*env)->ReleaseStringUTFChars(env, dest_name, cdest_name);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1connect_1or_1mod
+  (JNIEnv *env, jobject obj, jlong p, jstring src_name, jstring dest_name,
+   jobject jprops, jint modify)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *csrc_name = (*env)->GetStringUTFChars(env, src_name, 0);
+    const char *cdest_name = (*env)->GetStringUTFChars(env, dest_name, 0);
+
+    // process props
+    // don't bother letting user define signal types or lengths (will be overwritten)
+    mapper_db_connection_t cprops;
+    jstring expr_jstr = 0;
+    int src_length = 0, dest_length = 0;
+    char src_type = 0, dest_type = 0;
+    jobject src_min_field = NULL, src_max_field = NULL;
+    jobject dest_min_field = NULL, dest_max_field = NULL;
+    // TODO: "extra" props
+    int props_flags = 0;
+    if (jprops) {
+        jclass cls = (*env)->GetObjectClass(env, jprops);
+        if (cls) {
+            // mode
+            jfieldID fid = (*env)->GetFieldID(env, cls, "mode", "I");
+            if (fid) {
+                cprops.mode = (*env)->GetIntField(env, jprops, fid);
+                if ((int)cprops.mode >= 0)
+                    props_flags |= CONNECTION_MODE;
+            }
+            // expression
+            fid = (*env)->GetFieldID(env, cls, "expression", "Ljava/lang/String;");
+            if (fid) {
+                expr_jstr = (*env)->GetObjectField(env, jprops, fid);
+                if (expr_jstr) {
+                    cprops.expression = (char*)(*env)->GetStringUTFChars(env, expr_jstr, 0);
+                    props_flags |= CONNECTION_EXPRESSION;
                 }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
+            }
+            // bound_min
+            fid = (*env)->GetFieldID(env, cls, "boundMin", "I");
+            if (fid) {
+                cprops.bound_min = (*env)->GetIntField(env, jprops, fid);
+                if ((int)cprops.bound_min >= 0)
+                    props_flags |= CONNECTION_BOUND_MIN;
+            }
+            // bound_max
+            fid = (*env)->GetFieldID(env, cls, "boundMax", "I");
+            if (fid) {
+                cprops.bound_max = (*env)->GetIntField(env, jprops, fid);
+                if ((int)cprops.bound_max >= 0)
+                    props_flags |= CONNECTION_BOUND_MAX;
+            }
+            // src_min
+            fid = (*env)->GetFieldID(env, cls, "srcMin", "LMapper/PropertyValue;");
+            if (fid) {
+                src_min_field = (*env)->GetObjectField(env, jprops, fid);
+                if (src_min_field) {
+                    src_length = get_PropertyValue_elements(env, src_min_field,
+                                                            &cprops.src_min,
+                                                            &src_type);
+                    if (src_length) {
+                        cprops.src_length = src_length;
+                        cprops.src_type = src_type;
+                        props_flags |= (CONNECTION_RANGE_SRC_MIN
+                                        | CONNECTION_SRC_LENGTH
+                                        | CONNECTION_SRC_TYPE);
+                    }
+                }
+            }
+            // src_max
+            fid = (*env)->GetFieldID(env, cls, "srcMax", "LMapper/PropertyValue;");
+            if (fid) {
+                src_max_field = (*env)->GetObjectField(env, jprops, fid);
+                if (src_max_field) {
+                    char type;
+                    int length = get_PropertyValue_elements(env, src_max_field,
+                                                            &cprops.src_max,
+                                                            &type);
+                    if (length) {
+                        // check if length and type match, abort or cast otherwise
+                        if (src_length && length != src_length)
+                            printf("differing lengths for src!\n");
+                        else if (src_type && type != src_type)
+                            printf("differing types for src!\n");
+                        else {
+                            cprops.src_length = length;
+                            cprops.src_type = type;
+                            props_flags |= (CONNECTION_RANGE_SRC_MAX
+                                            | CONNECTION_SRC_LENGTH
+                                            | CONNECTION_SRC_TYPE);
+                        }
+                    }
+                }
+            }
+            // dest_min
+            fid = (*env)->GetFieldID(env, cls, "destMin", "LMapper/PropertyValue;");
+            if (fid) {
+                dest_min_field = (*env)->GetObjectField(env, jprops, fid);
+                if (dest_min_field) {
+                    dest_length = get_PropertyValue_elements(env, dest_min_field,
+                                                             &cprops.dest_min,
+                                                             &dest_type);
+                    if (dest_length) {
+                        cprops.dest_length = dest_length;
+                        cprops.dest_type = dest_type;
+                        props_flags |= (CONNECTION_RANGE_DEST_MIN
+                                        | CONNECTION_DEST_LENGTH
+                                        | CONNECTION_DEST_TYPE);
+                    }
+                }
+            }
+            // dest_max
+            fid = (*env)->GetFieldID(env, cls, "destMax", "LMapper/PropertyValue;");
+            if (fid) {
+                dest_max_field = (*env)->GetObjectField(env, jprops, fid);
+                if (dest_max_field) {
+                    char type;
+                    int length = get_PropertyValue_elements(env, dest_max_field,
+                                                            &cprops.dest_max,
+                                                            &type);
+                    if (length) {
+                        if (dest_length && length != dest_length)
+                            printf("differing lengths for dest!\n");
+                        else if (dest_type && type != dest_type)
+                            printf("differing types for dest!\n");
+                        else {
+                            // check if length and type match, abort or cast otherwise
+                            cprops.dest_length = length;
+                            cprops.dest_type = type;
+                            props_flags |= (CONNECTION_RANGE_DEST_MAX
+                                            | CONNECTION_DEST_LENGTH
+                                            | CONNECTION_DEST_TYPE);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (modify)
+        mapper_monitor_connection_modify(mon, csrc_name, cdest_name,
+                                         &cprops, props_flags);
+    else
+        mapper_monitor_connect(mon, csrc_name, cdest_name, &cprops, props_flags);
+    (*env)->ReleaseStringUTFChars(env, src_name, csrc_name);
+    (*env)->ReleaseStringUTFChars(env, dest_name, cdest_name);
+    if (expr_jstr)
+        (*env)->ReleaseStringUTFChars(env, expr_jstr, cprops.expression);
+    if (src_min_field)
+        release_PropertyValue_elements(env, src_min_field, cprops.src_min);
+    if (src_max_field)
+        release_PropertyValue_elements(env, src_max_field, cprops.src_max);
+    if (dest_min_field)
+        release_PropertyValue_elements(env, dest_min_field, cprops.dest_min);
+    if (dest_max_field)
+        release_PropertyValue_elements(env, dest_max_field, cprops.dest_max);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1disconnect
+  (JNIEnv *env, jobject obj, jlong p, jstring src_name, jstring dest_name)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    const char *csrc_name = (*env)->GetStringUTFChars(env, src_name, 0);
+    const char *cdest_name = (*env)->GetStringUTFChars(env, dest_name, 0);
+    mapper_monitor_disconnect(mon, csrc_name, cdest_name);
+    (*env)->ReleaseStringUTFChars(env, src_name, csrc_name);
+    (*env)->ReleaseStringUTFChars(env, dest_name, cdest_name);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_mmon_1autosubscribe
+  (JNIEnv *env, jobject obj, jlong p, jint flags)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    mapper_monitor_autosubscribe(mon, flags);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_mmon_1now
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_monitor mon = (mapper_monitor)ptr_jlong(p);
+    mapper_timetag_t tt;
+    mapper_monitor_now(mon, &tt);
+    jobject o = get_jobject_from_timetag(genv, &tt);
+    return o;
+}
+
+/**** Mapper.Monitor.Db ****/
+
+static void java_db_device_cb(mapper_db_device record,
+                              mapper_db_action_t action,
+                              void *user_data)
+{
+    if (bailing || !user_data)
+        return;
+
+    // Create a wrapper class for the device properties
+    jclass cls = (*genv)->FindClass(genv, "Mapper/Db/Device");
+    if (!cls)
+    return;
+    jmethodID mid = (*genv)->GetMethodID(genv, cls, "<init>", "(J)V");
+    jobject devdbobj = (*genv)->NewObject(genv, cls, mid, jlong_ptr(record));
+
+    jobject obj = (jobject)user_data;
+    cls = (*genv)->GetObjectClass(genv, user_data);
+    if (cls) {
+        mid = (*genv)->GetMethodID(genv, cls, "onEvent",
+                                   "(LMapper/Db/Device;I)V");
+        if (mid) {
+            (*genv)->CallVoidMethod(genv, obj, mid, devdbobj, action);
+            if ((*genv)->ExceptionOccurred(genv))
+                bailing = 1;
+        }
+        else {
+            printf("Did not successfully look up onEvent method.\n");
+        }
+    }
+}
+
+static void java_db_signal_cb(mapper_db_signal record,
+                              mapper_db_action_t action,
+                              void *user_data)
+{
+    if (bailing || !user_data)
+        return;
+
+    // Create a wrapper class for the signal properties
+    jclass cls = (*genv)->FindClass(genv, "Mapper/Db/Signal");
+    if (!cls)
+        return;
+
+    jmethodID mid = (*genv)->GetMethodID(genv, cls, "<init>", "(J)V");
+    jobject sigdbobj = (*genv)->NewObject(genv, cls, mid, jlong_ptr(record));
+
+    jobject obj = (jobject)user_data;
+    cls = (*genv)->GetObjectClass(genv, user_data);
+    if (cls) {
+        mid = (*genv)->GetMethodID(genv, cls, "onEvent",
+                                   "(LMapper/Db/Signal;I)V");
+        if (mid) {
+            (*genv)->CallVoidMethod(genv, obj, mid, sigdbobj, action);
+            if ((*genv)->ExceptionOccurred(genv))
+                bailing = 1;
+        }
+        else {
+            printf("Did not successfully look up onEvent method.\n");
+        }
+    }
+}
+
+static void java_db_link_cb(mapper_db_link record,
+                            mapper_db_action_t action,
+                            void *user_data)
+{
+    if (bailing || !user_data)
+    return;
+
+    // Create a wrapper class for the link properties
+    jclass cls = (*genv)->FindClass(genv, "Mapper/Db/Link");
+    if (!cls)
+        return;
+
+    jmethodID mid = (*genv)->GetMethodID(genv, cls, "<init>", "(J)V");
+    jobject linkdbobj = (*genv)->NewObject(genv, cls, mid, jlong_ptr(record));
+
+    jobject obj = (jobject)user_data;
+    cls = (*genv)->GetObjectClass(genv, user_data);
+    if (cls) {
+        mid = (*genv)->GetMethodID(genv, cls, "onEvent",
+                                   "(LMapper/Db/Link;I)V");
+        if (mid) {
+            (*genv)->CallVoidMethod(genv, obj, mid, linkdbobj, action);
+            if ((*genv)->ExceptionOccurred(genv))
+            bailing = 1;
+        }
+        else {
+            printf("Did not successfully look up onEvent method.\n");
+        }
+    }
+}
+
+static void java_db_connection_cb(mapper_db_connection record,
+                                  mapper_db_action_t action,
+                                  void *user_data)
+{
+    if (bailing || !user_data)
+        return;
+
+    // Create a wrapper class for the connection properties
+    jclass cls = (*genv)->FindClass(genv, "Mapper/Db/Connection");
+    if (!cls)
+        return;
+
+    jmethodID mid = (*genv)->GetMethodID(genv, cls, "<init>", "(J)V");
+    jobject condbobj = (*genv)->NewObject(genv, cls, mid, jlong_ptr(record));
+
+    jobject obj = (jobject)user_data;
+    cls = (*genv)->GetObjectClass(genv, user_data);
+    if (cls) {
+        mid = (*genv)->GetMethodID(genv, cls, "onEvent",
+                                   "(LMapper/Db/Connection;I)V");
+        if (mid) {
+            (*genv)->CallVoidMethod(genv, obj, mid, condbobj, action);
+            if ((*genv)->ExceptionOccurred(genv))
+                bailing = 1;
+        }
+        else {
+            printf("Did not successfully look up onEvent method.\n");
+        }
+    }
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1add_1device_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    // TODO: check listener hasn't already been added
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    jobject o = (*env)->NewGlobalRef(env, listener);
+    mapper_db_add_device_callback(db, java_db_device_cb, o);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1remove_1device_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    mapper_db_remove_device_callback(db, java_db_device_cb, listener);
+    (*env)->DeleteGlobalRef(env, listener);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1add_1signal_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    // TODO: check listener hasn't already been added
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    jobject o = (*env)->NewGlobalRef(env, listener);
+    mapper_db_add_signal_callback(db, java_db_signal_cb, o);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1remove_1signal_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    mapper_db_remove_signal_callback(db, java_db_signal_cb, ptr_jlong(listener));
+    (*env)->DeleteGlobalRef(env, listener);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1add_1link_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    // TODO: check listener hasn't already been added
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+
+    jobject o = (*env)->NewGlobalRef(env, listener);
+    mapper_db_add_link_callback(db, java_db_link_cb, o);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1remove_1link_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    mapper_db_remove_link_callback(db, java_db_link_cb, ptr_jlong(listener));
+    (*env)->DeleteGlobalRef(env, listener);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1add_1connection_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    // TODO: check listener hasn't already been added
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+
+    jobject o = (*env)->NewGlobalRef(env, listener);
+    mapper_db_add_connection_callback(db, java_db_connection_cb, o);
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Monitor_00024Db_mdb_1remove_1connection_1callback
+  (JNIEnv *env, jobject obj, jlong p, jobject listener)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db || !listener)
+        return;
+    mapper_db_remove_connection_callback(db, java_db_connection_cb,
+                                         ptr_jlong(listener));
+    (*env)->DeleteGlobalRef(env, listener);
+}
+
+/**** Mapper.Db.Device ****/
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->name);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1ordinal
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->ordinal;
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1host
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->host);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1port
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->port;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1inputs
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_inputs;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1outputs
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_outputs;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1links_1in
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_links_in;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1links_1out
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_links_out;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1connections_1in
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_connections_in;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1num_1connections_1out
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->num_connections_out;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1version
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    return props->version;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Device_mdb_1device_1get_1synced
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    jobject o = get_jobject_from_timetag(genv, &props->synced);
+    return o;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Device_mdb_1device_1property_1lookup
+  (JNIEnv *env, jobject obj, jlong p, jstring property)
+{
+    mapper_db_device props = (mapper_db_device)ptr_jlong(p);
+    const char *cprop = (*env)->GetStringUTFChars(env, property, 0);
+    char type;
+    int length;
+    const void *value;
+    jobject o = 0;
+
+    if (!mapper_db_device_property_lookup(props, cprop, &type, &value, &length))
+        o = build_PropertyValue(env, type, value, length);
+
+    (*env)->ReleaseStringUTFChars(env, property, cprop);
+    return o;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_devices
+  (JNIEnv *env, jobject obj)
+{
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    mapper_db_device *devs = mapper_db_get_all_devices(db);
+    if (!devs) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/DeviceCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(devs));
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_get_1device
+  (JNIEnv *env, jobject obj, jstring s)
+{
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *name = (*env)->GetStringUTFChars(env, s, 0);
+    mapper_db_device dev = mapper_db_get_device_by_name(db, name);
+    (*env)->ReleaseStringUTFChars(env, s, name);
+    if (!dev) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/Device");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(dev));
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_match_1devices
+  (JNIEnv *env, jobject obj, jstring s)
+{
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *pattern = (*env)->GetStringUTFChars(env, s, 0);
+    mapper_db_device *devs = mapper_db_match_devices_by_name(db, pattern);
+    (*env)->ReleaseStringUTFChars(env, s, pattern);
+    if (!devs) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/DeviceCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(devs));
+}
+
+/**** Mapper.Db.Device.Iterator ****/
+
+JNIEXPORT jlong JNICALL Java_Mapper_Db_DeviceIterator_mdb_1deref
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    void **ptr = (void**)ptr_jlong(p);
+    return jlong_ptr(*ptr);
+}
+
+JNIEXPORT jlong JNICALL Java_Mapper_Db_DeviceIterator_mdb_1device_1next
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device_t **devs = (mapper_db_device_t**)ptr_jlong(p);
+    return jlong_ptr(mapper_db_device_next(devs));
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Db_DeviceIterator_mdb_1device_1done
+(JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_device_t **devs = (mapper_db_device_t**)ptr_jlong(p);
+    if (devs)
+        mapper_db_device_done(devs);
+}
+
+/**** Mapper.Db.Signal ****/
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->name);
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1device_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->device_name);
+}
+
+JNIEXPORT jboolean JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1is_1output
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return props->is_output!=0;
+}
+
+JNIEXPORT jchar JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1type
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return props->type;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1length
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return props->length;
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1unit
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->unit);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1minimum
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+
+    if (!props->minimum)
+        return 0;
+    return build_PropertyValue(env, props->type, props->minimum, props->length);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1maximum
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+
+    if (!props->maximum)
+        return 0;
+    return build_PropertyValue(env, props->type, props->maximum, props->length);
+}
+
+JNIEXPORT jdouble JNICALL Java_Mapper_Db_Signal_mdb_1signal_1get_1rate
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    return (jdouble)props->rate;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_mdb_1signal_1property_1lookup
+  (JNIEnv *env, jobject obj, jlong p, jstring property)
+{
+    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    const char *cprop = (*env)->GetStringUTFChars(env, property, 0);
+    char type;
+    int length;
+    const void *value;
+    jobject o = 0;
 
-            case 'f': {
-                float *value = msig_value(sig, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
+    if (!mapper_db_signal_property_lookup(props, cprop, &type, &value, &length))
+        o = build_PropertyValue(env, type, value, length);
 
-            case 'd': {
-                double *value = msig_value(sig, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
-        }
+    (*env)->ReleaseStringUTFChars(env, property, cprop);
+    return o;
+}
 
-        (*env)->ReleaseDoubleArrayElements(env, ar, array, 0);
+JNIEXPORT jlong JNICALL Java_Mapper_Monitor_00024Db_mdb_1inputs
+  (JNIEnv *env, jobject obj, jlong p, jstring s)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    mapper_db_signal *sigs;
+    if (s) {
+        const char *name = (*env)->GetStringUTFChars(env, s, 0);
+        sigs = mapper_db_get_inputs_by_device_name(db, name);
+        (*env)->ReleaseStringUTFChars(env, s, name);
     }
-
-    if (objtt) {
-        jclass cls = (*env)->GetObjectClass(env, objtt);
-        if (cls) {
-            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
-            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
-            if (sec && frac) {
-                (*env)->SetLongField(env, objtt, sec, tt.sec);
-                (*env)->SetLongField(env, objtt, frac, tt.frac);
-            }
-        }
+    else {
+        sigs = mapper_db_get_all_inputs(db);
     }
+    return jlong_ptr(sigs);
+}
 
-    return JNI_TRUE;
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_getInput
+  (JNIEnv *env, jobject obj, jstring s1, jstring s2)
+{
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *sig_name = (*env)->GetStringUTFChars(env, s2, 0);
+    mapper_db_signal sig =
+        mapper_db_get_input_by_device_and_signal_names(db, dev_name, sig_name);
+    (*env)->ReleaseStringUTFChars(env, s1, dev_name);
+    (*env)->ReleaseStringUTFChars(env, s2, sig_name);
+    if (!sig) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/Signal");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(sig));
 }
 
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instance_1value__I_3ILMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jintArray ar, jobject objtt)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_matchInputs
+  (JNIEnv *env, jobject obj, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
-    mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
 
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
+    const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *sig_pattern = (*env)->GetStringUTFChars(env, s2, 0);
+    mapper_db_signal *sigs = mapper_db_match_inputs_by_device_name(db, dev_name,
+                                                                   sig_pattern);
+    (*env)->ReleaseStringUTFChars(env, s1, dev_name);
+    (*env)->ReleaseStringUTFChars(env, s2, sig_pattern);
+    if (!sigs) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/SignalCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(sigs));
+}
+
+JNIEXPORT jlong JNICALL Java_Mapper_Monitor_00024Db_mdb_1outputs
+(JNIEnv *env, jobject obj, jlong p, jstring s)
+{
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    mapper_db_signal *sigs;
+    if (s) {
+        const char *name = (*env)->GetStringUTFChars(env, s, 0);
+        sigs = mapper_db_get_outputs_by_device_name(db, name);
+        (*env)->ReleaseStringUTFChars(env, s, name);
     }
-    if (props->type != 'i') {
-        throwIllegalArgumentTruncate(env, sig);
-        return JNI_FALSE;
+    else {
+        sigs = mapper_db_get_all_outputs(db);
     }
+    return jlong_ptr(sigs);
+}
 
-    int *value = msig_instance_value(sig, id, &tt);
-    if (!value)
-        return JNI_FALSE;
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_getOutput
+(JNIEnv *env, jobject obj, jstring s1, jstring s2)
+{
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
 
-    jint *array = (*env)->GetIntArrayElements(env, ar, 0);
-    if (array) {
-        int i;
-        for (i=0; i < props->length; i++)
-            array[i] = (jint)value[i];
-        (*env)->ReleaseIntArrayElements(env, ar, array, 0);
-    }
+    const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *sig_name = (*env)->GetStringUTFChars(env, s2, 0);
+    mapper_db_signal sig =
+    mapper_db_get_output_by_device_and_signal_names(db, dev_name, sig_name);
+    (*env)->ReleaseStringUTFChars(env, s1, dev_name);
+    (*env)->ReleaseStringUTFChars(env, s2, sig_name);
+    if (!sig) return 0;
 
-    if (objtt) {
-        jclass cls = (*env)->GetObjectClass(env, objtt);
-        if (cls) {
-            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
-            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
-            if (sec && frac) {
-                (*env)->SetLongField(env, objtt, sec, tt.sec);
-                (*env)->SetLongField(env, objtt, frac, tt.frac);
-            }
-        }
-    }
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/Signal");
+    if (!cls) return 0;
 
-    return JNI_TRUE;
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(sig));
 }
 
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instance_1value__I_3FLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jfloatArray ar, jobject objtt)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_matchOutputs
+(JNIEnv *env, jobject obj, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
-    mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
 
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
-    }
+    const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *sig_pattern = (*env)->GetStringUTFChars(env, s2, 0);
+    mapper_db_signal *sigs = mapper_db_match_outputs_by_device_name(db, dev_name,
+                                                                    sig_pattern);
+    (*env)->ReleaseStringUTFChars(env, s1, dev_name);
+    (*env)->ReleaseStringUTFChars(env, s2, sig_pattern);
+    if (!sigs) return 0;
 
-    jfloat *array = (*env)->GetFloatArrayElements(env, ar, 0);
-    if (array)
-    {
-        int i;
-        switch (props->type)
-        {
-            case 'i': {
-                int *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
-            } break;
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/SignalCollection");
+    if (!cls) return 0;
 
-            case 'f': {
-                float *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
-            } break;
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    return (*env)->NewObject(env, cls, mid, jlong_ptr(sigs));
+}
 
-            case 'd': {
-                double *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseFloatArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jfloat)value[i];
-            } break;
-        }
+/**** Mapper.Db.Signal.Iterator ****/
 
-        (*env)->ReleaseFloatArrayElements(env, ar, array, 0);
-    }
+JNIEXPORT jlong JNICALL Java_Mapper_Db_SignalIterator_mdb_1deref
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    void **ptr = (void**)ptr_jlong(p);
+    return jlong_ptr(*ptr);
+}
 
-    if (objtt) {
-        jclass cls = (*env)->GetObjectClass(env, objtt);
+JNIEXPORT jlong JNICALL Java_Mapper_Db_SignalIterator_mdb_1signal_1next
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal_t **sigs = (mapper_db_signal_t**)ptr_jlong(p);
+    return jlong_ptr(mapper_db_signal_next(sigs));
+}
+
+JNIEXPORT void JNICALL Java_Mapper_Db_SignalIterator_mdb_1signal_1done
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_signal_t **sigs = (mapper_db_signal_t**)ptr_jlong(p);
+    if (sigs)
+        mapper_db_signal_done(sigs);
+}
+
+/**** Mapper.Db.Link ****/
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1src_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->src_name);
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1dest_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->dest_name);
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1src_1host
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->src_host);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1src_1port
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return props->src_port;
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1dest_1host
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->dest_host);
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1dest_1port
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return props->dest_port;
+}
+
+JNIEXPORT jint JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1num_1scopes
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    return props->num_scopes;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Link_mdb_1link_1get_1scope_1names
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+
+    if (!props->num_scopes)
+        return 0;
+    return build_PropertyValue(env, 's', props->scope_names, props->num_scopes);
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Link_mapper_1db_1link_1property_1lookup
+  (JNIEnv *env, jobject obj, jlong p, jstring property)
+{
+    mapper_db_link props = (mapper_db_link)ptr_jlong(p);
+    const char *cprop = (*env)->GetStringUTFChars(env, property, 0);
+    char type;
+    int length;
+    const void *value;
+    jobject o = 0;
+
+    if (!mapper_db_link_property_lookup(props, cprop, &type, &value, &length))
+        o = build_PropertyValue(env, type, value, length);
+
+    (*env)->ReleaseStringUTFChars(env, property, cprop);
+    return o;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_getLink
+  (JNIEnv *env, jobject obj, jstring s1, jstring s2)
+{
+    jobject linkobj = 0;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *src_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *dest_name = (*env)->GetStringUTFChars(env, s2, 0);
+    mapper_db_link link = mapper_db_get_link_by_src_dest_names(db, src_name, dest_name);
+    if (link) {;
+        jclass cls = (*env)->FindClass(env, "Mapper/Db/Link");
         if (cls) {
-            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
-            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
-            if (sec && frac) {
-                (*env)->SetLongField(env, objtt, sec, tt.sec);
-                (*env)->SetLongField(env, objtt, frac, tt.frac);
-            }
+            jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+            linkobj = (*env)->NewObject(env, cls, mid, jlong_ptr(link));
         }
     }
-
-    return JNI_TRUE;
+    (*env)->ReleaseStringUTFChars(env, s1, src_name);
+    (*env)->ReleaseStringUTFChars(env, s2, dest_name);
+    return linkobj;
 }
 
-JNIEXPORT jboolean JNICALL Java_Mapper_Device_00024Signal_instance_1value__I_3DLMapper_TimeTag_2
-  (JNIEnv *env, jobject obj, jint id, jdoubleArray ar, jobject objtt)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_mdb_1links
+  (JNIEnv *env, jobject obj, jlong p, jstring s)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return JNI_FALSE;
-    mapper_db_signal props = msig_properties(sig);
-    mapper_timetag_t tt;
+    mapper_db_link *links;
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db) return 0;
 
-    int length = (*env)->GetArrayLength(env, ar);
-    if (length < props->length) {
-        throwIllegalArgumentLength(env, sig, length);
-        return JNI_FALSE;
+    if (s) {
+        const char *name = (*env)->GetStringUTFChars(env, s, 0);
+        links = mapper_db_get_links_by_device_name(db, name);
+        (*env)->ReleaseStringUTFChars(env, s, name);
+    }
+    else {
+        links = mapper_db_get_all_links(db);
     }
 
-    jdouble *array = (*env)->GetDoubleArrayElements(env, ar, 0);
-    if (array)
-    {
-        int i;
-        switch (props->type)
-        {
-            case 'i': {
-                int *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
+    if (!links) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/LinkCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject linksobj = (*env)->NewObject(env, cls, mid, jlong_ptr(links));
+    return linksobj;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_links
+  (JNIEnv *env, jobject obj, jobject srcobj, jobject destobj)
+{
+    mapper_db_link *links;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    // retrieve mapper_db_device* ptrs from DeviceCollection objects
+    mapper_db_device *src = get_db_device_ptr_from_jobject(env, srcobj);
+    mapper_db_device *dest = get_db_device_ptr_from_jobject(env, destobj);
+    if (!src || !dest) return 0;
+
+    links = mapper_db_get_links_by_src_dest_devices(db, src, dest);
+    if (!links) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/LinkCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject linksobj = (*env)->NewObject(env, cls, mid, jlong_ptr(links));
+    return linksobj;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_linksBySrc
+  (JNIEnv *env, jobject obj, jstring s)
+{
+    mapper_db_link *links;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *name = (*env)->GetStringUTFChars(env, s, 0);
+    links = mapper_db_get_links_by_src_device_name(db, name);
+    (*env)->ReleaseStringUTFChars(env, s, name);
+
+    if (!links) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/LinkCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject linksobj = (*env)->NewObject(env, cls, mid, jlong_ptr(links));
+    return linksobj;
+}
+
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_linksByDest
+  (JNIEnv *env, jobject obj, jstring s)
+{
+    mapper_db_link *links;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
+
+    const char *name = (*env)->GetStringUTFChars(env, s, 0);
+    links = mapper_db_get_links_by_dest_device_name(db, name);
+    (*env)->ReleaseStringUTFChars(env, s, name);
+
+    if (!links) return 0;
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/LinkCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject linksobj = (*env)->NewObject(env, cls, mid, jlong_ptr(links));
+    return linksobj;
+}
+
+/**** Mapper.Db.Link.Iterator ****/
 
-            case 'f': {
-                float *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
+JNIEXPORT jlong JNICALL Java_Mapper_Db_LinkIterator_mdb_1deref
+(JNIEnv *env, jobject obj, jlong p)
+{
+    void **ptr = (void**)ptr_jlong(p);
+    return jlong_ptr(*ptr);
+}
 
-            case 'd': {
-                double *value = msig_instance_value(sig, id, &tt);
-                if (!value) {
-                    (*env)->ReleaseDoubleArrayElements(env, ar, array, JNI_ABORT);
-                    return JNI_FALSE;
-                }
-                for (i=0; i < props->length; i++)
-                    array[i] = (jdouble)value[i];
-            } break;
-        }
+JNIEXPORT jlong JNICALL Java_Mapper_Db_LinkIterator_mdb_1link_1next
+(JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link_t **links = (mapper_db_link_t**)ptr_jlong(p);
+    return jlong_ptr(mapper_db_link_next(links));
+}
 
-        (*env)->ReleaseDoubleArrayElements(env, ar, array, 0);
-    }
+JNIEXPORT void JNICALL Java_Mapper_Db_LinkIterator_mdb_1link_1done
+(JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_link_t **links = (mapper_db_link_t**)ptr_jlong(p);
+    if (links)
+        mapper_db_link_done(links);
+}
 
-    if (objtt) {
-        jclass cls = (*env)->GetObjectClass(env, objtt);
-        if (cls) {
-            jfieldID sec = (*env)->GetFieldID(env, cls, "sec", "J");
-            jfieldID frac = (*env)->GetFieldID(env, cls, "frac", "J");
-            if (sec && frac) {
-                (*env)->SetLongField(env, objtt, sec, tt.sec);
-                (*env)->SetLongField(env, objtt, frac, tt.frac);
-            }
-        }
-    }
+/**** Mapper.Db.Connection ****/
 
-    return JNI_TRUE;
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1src_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->src_name);
 }
 
-/**** Mapper.Db.Signal ****/
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1dest_1name
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return (*env)->NewStringUTF(env, props->dest_name);
+}
 
-JNIEXPORT void JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1set_1name
-  (JNIEnv *env, jobject obj, jlong p, jstring name)
+JNIEXPORT jchar JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1src_1type
+  (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    const char *cname = (*env)->GetStringUTFChars(env, name, 0);
-    free((char*)props->name);
-    props->name = strdup(cname);
-    (*env)->ReleaseStringUTFChars(env, name, cname);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->src_type;
 }
 
-JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1name
+JNIEXPORT jchar JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1dest_1type
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return (*env)->NewStringUTF(env, props->name);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->dest_type;
 }
 
-JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1device_1name
+JNIEXPORT jint JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1src_1length
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return (*env)->NewStringUTF(env, props->device_name);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->src_length;
 }
 
-JNIEXPORT jboolean JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1is_1output
+JNIEXPORT jint JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1dest_1length
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return props->is_output!=0;
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->dest_length;
 }
 
-JNIEXPORT jchar JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1type
+JNIEXPORT jint JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1bound_1min
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return props->type!=0;
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->bound_min;
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1length
+JNIEXPORT jint JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1bound_1max
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return props->length;
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->bound_max;
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1set_1unit
-  (JNIEnv *env, jobject obj, jlong p, jstring unit)
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1src_1min
+  (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    const char *cunit = (*env)->GetStringUTFChars(env, unit, 0);
-    free((char*)props->unit);
-    props->unit = strdup(cunit);
-    (*env)->ReleaseStringUTFChars(env, unit, cunit);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+
+    if (!props->src_min)
+        return 0;
+    return build_PropertyValue(env, props->src_type, props->src_min, props->src_length);
 }
 
-JNIEXPORT jstring JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1unit
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1src_1max
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return (*env)->NewStringUTF(env, props->unit);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+
+    if (!props->src_max)
+        return 0;
+    return build_PropertyValue(env, props->src_type, props->src_max, props->src_length);
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1minimum
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1dest_1min
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
 
-    if (!props->minimum)
+    if (!props->dest_min)
         return 0;
-    return build_PropertyValue(env, props->type, props->minimum, props->length);
+    return build_PropertyValue(env, props->dest_type, props->dest_min, props->dest_length);
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1maximum
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1dest_1max
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
 
-    if (!props->maximum)
+    if (!props->dest_max)
         return 0;
-    return build_PropertyValue(env, props->type, props->maximum, props->length);
+    return build_PropertyValue(env, props->dest_type, props->dest_max, props->dest_length);
 }
 
-JNIEXPORT jdouble JNICALL Java_Mapper_Db_Signal_msig_1db_1signal_1get_1rate
+JNIEXPORT jint JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1mode
   (JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
-    return (jdouble)props->rate;
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    return props->mode;
+}
+
+JNIEXPORT jstring JNICALL Java_Mapper_Db_Connection_mdb_1connection_1get_1expression
+  (JNIEnv *env, jobject obj, jlong p)
+{
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
+    if (props->expression)
+        return (*env)->NewStringUTF(env, props->expression);
+    return 0;
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Db_Signal_mapper_1db_1signal_1property_1lookup
+JNIEXPORT jobject JNICALL Java_Mapper_Db_Connection_mapper_1db_1connection_1property_1lookup
   (JNIEnv *env, jobject obj, jlong p, jstring property)
 {
-    mapper_db_signal props = (mapper_db_signal)ptr_jlong(p);
+    mapper_db_connection props = (mapper_db_connection)ptr_jlong(p);
     const char *cprop = (*env)->GetStringUTFChars(env, property, 0);
     char type;
     int length;
     const void *value;
     jobject o = 0;
 
-    if (!mapper_db_signal_property_lookup(props, cprop, &type, &value, &length))
+    if (!mapper_db_connection_property_lookup(props, cprop, &type, &value, &length))
         o = build_PropertyValue(env, type, value, length);
 
     (*env)->ReleaseStringUTFChars(env, property, cprop);
     return o;
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_set_1instance_1event_1callback
-  (JNIEnv *env, jobject obj, jobject handler, jint flags)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_mdb_1connections
+  (JNIEnv *env, jobject obj, jlong p, jstring s)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-
-    mapper_db_signal props = msig_properties(sig);
-    msig_jni_context ctx = (msig_jni_context)props->user_data;
+    mapper_db_connection *cons;
+    mapper_db db = (mapper_db)ptr_jlong(p);
+    if (!db) return 0;
 
-    if (ctx->instanceHandler)
-        (*env)->DeleteGlobalRef(env, ctx->instanceHandler);
-    if (handler) {
-        ctx->instanceHandler = (*env)->NewGlobalRef(env, handler);
-        msig_set_instance_event_callback(sig,
-                                         msig_instance_event_cb,
-                                         flags, ctx);
+    if (s) {
+        const char *name = (*env)->GetStringUTFChars(env, s, 0);
+        cons = mapper_db_get_connections_by_device_name(db, name);
+        (*env)->ReleaseStringUTFChars(env, s, name);
     }
     else {
-        ctx->instanceHandler = 0;
-        msig_set_instance_event_callback(sig, 0, flags, 0);
+        cons = mapper_db_get_all_connections(db);
     }
-}
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_set_1callback
-  (JNIEnv *env, jobject obj, jobject handler)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
+    if (!cons) return 0;
 
-    mapper_db_signal props = msig_properties(sig);
-    msig_jni_context ctx = (msig_jni_context)props->user_data;
-    if (ctx->listener)
-        (*env)->DeleteGlobalRef(env, ctx->listener);
-    if (handler) {
-        ctx->listener = (*env)->NewGlobalRef(env, handler);
-        msig_set_callback(sig, java_msig_input_cb, ctx);
-    }
-    else {
-        ctx->listener = 0;
-        msig_set_callback(sig, 0, 0);
-    }
-}
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/ConnectionCollection");
+    if (!cls) return 0;
 
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject consobj = (*env)->NewObject(env, cls, mid, jlong_ptr(cons));
+    return consobj;
+}
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_set_1instance_1callback
-  (JNIEnv *env, jobject obj, jint instance_id, jobject data)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_connections
+  (JNIEnv *env, jobject obj, jobject srcobj, jobject destobj)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return;
-    jobject prev = (jobject)msig_get_instance_data(sig, instance_id);
-    if (prev)
-        (*env)->DeleteGlobalRef(env, prev);
+    mapper_db_connection *cons;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db) return 0;
 
-    // Note that msig_set_instance_data() can trigger the instance
-    // event callback, so we need to use the global to pass the
-    // environment to the handler.
-    genv = env;
-    bailing = 0;
+    // retrieve mapper_db_signal* ptrs from SignalCollection objects
+    mapper_db_signal *src = get_db_signal_ptr_from_jobject(env, srcobj);
+    mapper_db_signal *dest = get_db_signal_ptr_from_jobject(env, destobj);
+    if (!src || !dest) return 0;
 
-    msig_set_instance_data(sig, instance_id,
-                           data ? (*env)->NewGlobalRef(env, data) : 0);
-}
+    cons = mapper_db_get_connections_by_signal_queries(db, src, dest);
+    if (!cons) return 0;
 
-JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_get_1instance_1callback
-  (JNIEnv *env, jobject obj, jint instance_id)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return 0;
-    return (jobject)msig_get_instance_data(sig, instance_id);
-}
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/ConnectionCollection");
+    if (!cls) return 0;
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_reserve_1instances__I
-  (JNIEnv *env, jobject obj, jint num)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return 0;
-    return msig_reserve_instances(sig, num, 0, 0);
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject consobj = (*env)->NewObject(env, cls, mid, jlong_ptr(cons));
+    return consobj;
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_reserve_1instances___3I
-(JNIEnv *env, jobject obj, jintArray ids)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_mdb_1connections_1by_1src
+  (JNIEnv *env, jobject obj, jlong p, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return 0;
-    int length = (*env)->GetArrayLength(env, ids);
-    jint *array = (*env)->GetIntArrayElements(env, ids, 0);
-    if (array) {
-        int reserved = msig_reserve_instances(sig, length, array, 0);
-        (*env)->ReleaseIntArrayElements(env, ids, array, JNI_ABORT);
-        return reserved;
+    mapper_db_connection *cons;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db || !s2) return 0;
+
+    const char *sig_name = (*env)->GetStringUTFChars(env, s2, 0);
+    if (s1) {
+        const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+        cons = mapper_db_get_connections_by_src_device_and_signal_names(db, dev_name,
+                                                                        sig_name);
+        (*env)->ReleaseStringUTFChars(env, s1, dev_name);
     }
-    return 0;
-}
+    else {
+        cons = mapper_db_get_connections_by_src_signal_name(db, sig_name);
+    }
+    (*env)->ReleaseStringUTFChars(env, s2, sig_name);
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_reserve_1instances__ILMapper_InputListener_2
-(JNIEnv *env, jobject obj, jint num, jobject data)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return 0;
-    jobject ref = data ? (*env)->NewGlobalRef(env, data) : 0;
-    int i, reserved = 0;
-    for (i=0; i<num; i++)
-        reserved += msig_reserve_instances(sig, 1, 0, (void**)&ref);
-    return reserved;
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/ConnectionCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject consobj = (*env)->NewObject(env, cls, mid, jlong_ptr(cons));
+    return consobj;
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_reserve_1instances___3ILMapper_InputListener_2
-(JNIEnv *env, jobject obj, jintArray ids, jobject data)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_mdb_1connections_1by_1dest
+  (JNIEnv *env, jobject obj, jlong p, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig)
-        return 0;
-    int length = (*env)->GetArrayLength(env, ids);
-    jint *array = (*env)->GetIntArrayElements(env, ids, 0);
-    if (array) {
-        jobject ref = data ? (*env)->NewGlobalRef(env, data) : 0;
-        int i, reserved = 0;
-        for (i=0; i<length; i++)
-            reserved += msig_reserve_instances(sig, 1, &array[i], (void**)&ref);
-        (*env)->ReleaseIntArrayElements(env, ids, array, JNI_ABORT);
-        return reserved;
+    mapper_db_connection *cons;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db || !s2) return 0;
+
+    const char *sig_name = (*env)->GetStringUTFChars(env, s2, 0);
+    if (s1) {
+        const char *dev_name = (*env)->GetStringUTFChars(env, s1, 0);
+        cons = mapper_db_get_connections_by_dest_device_and_signal_names(db, dev_name,
+                                                                         sig_name);
+        (*env)->ReleaseStringUTFChars(env, s1, dev_name);
     }
-    return 0;
-}
+    else {
+        cons = mapper_db_get_connections_by_dest_signal_name(db, sig_name);
+    }
+    (*env)->ReleaseStringUTFChars(env, s2, sig_name);
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_release_1instance
-  (JNIEnv *env, jobject obj, jint instance_id, jobject objtt)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-    mapper_timetag_t tt, *ptt=0;
-    ptt = get_timetag_from_jobject(env, objtt, &tt);
-    msig_release_instance(sig, instance_id, ptt ? *ptt : MAPPER_NOW);
-}
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/ConnectionCollection");
+    if (!cls) return 0;
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_remove_1instance
-(JNIEnv *env, jobject obj, jint instance_id)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-    msig_remove_instance(sig, instance_id);
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject consobj = (*env)->NewObject(env, cls, mid, jlong_ptr(cons));
+    return consobj;
 }
 
-JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_oldest_1active_1instance
-  (JNIEnv *env, jobject obj)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_connectionBySignals
+  (JNIEnv *env, jobject obj, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    int i, r = msig_get_oldest_active_instance(sig, &i);
-    jobject iobj = 0;
-    if (r == 0) {
-        jclass cls = (*env)->FindClass(env, "java/lang/Integer");
-        if (cls) {
-            jmethodID cons = (*env)->GetMethodID(env, cls,
-                                                  "<init>", "(I)V");
-            if (cons) {
-                iobj = (*env)->NewObject(env, cls, cons, i);
-                return iobj;
-            }
-        }
-    }
-    return 0;
-}
+    mapper_db_connection con;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db || !s1 || !s2) return 0;
 
-JNIEXPORT jobject JNICALL Java_Mapper_Device_00024Signal_newest_1active_1instance
-  (JNIEnv *env, jobject obj)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    int i, r = msig_get_newest_active_instance(sig, &i);
-    jobject iobj = 0;
-    if (r == 0) {
-        jclass cls = (*env)->FindClass(env, "java/lang/Integer");
-        if (cls) {
-            jmethodID cons = (*env)->GetMethodID(env, cls,
-                                                 "<init>", "(I)V");
-            if (cons) {
-                iobj = (*env)->NewObject(env, cls, cons, i);
-                return iobj;
-            }
-        }
-    }
-    return 0;
-}
+    const char *src_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *dest_name = (*env)->GetStringUTFChars(env, s2, 0);
+    con = mapper_db_get_connection_by_signal_full_names(db, src_name, dest_name);
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_match_1instances
-  (JNIEnv *env, jobject obj, jobject from, jobject to, jint instance_id)
-{
-    mapper_signal sigfrom = get_signal_from_jobject(env, from);
-    mapper_signal sigto = get_signal_from_jobject(env, to);
-    if (!sigto || !sigfrom) return;
+    (*env)->ReleaseStringUTFChars(env, s1, src_name);
+    (*env)->ReleaseStringUTFChars(env, s2, dest_name);
 
-    msig_match_instances(sigfrom, sigto, instance_id);
-}
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/Connection");
+    if (!cls) return 0;
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_num_1active_1instances
-  (JNIEnv *env, jobject obj)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    return msig_num_active_instances(sig);
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject conobj = (*env)->NewObject(env, cls, mid, jlong_ptr(con));
+    return conobj;
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_num_1reserved_1instances
-  (JNIEnv *env, jobject obj)
+JNIEXPORT jobject JNICALL Java_Mapper_Monitor_00024Db_connectionsByDevices
+  (JNIEnv *env, jobject obj, jstring s1, jstring s2)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    return msig_num_reserved_instances(sig);
-}
+    mapper_db_connection *cons;
+    mapper_db db = get_db_from_jobject(env, obj);
+    if (!db || !s1 || !s2) return 0;
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_active_1instance_1id
-  (JNIEnv *env, jobject obj, jint index)
-{
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    return msig_active_instance_id(sig, index);
+    const char *src_name = (*env)->GetStringUTFChars(env, s1, 0);
+    const char *dest_name = (*env)->GetStringUTFChars(env, s2, 0);
+    cons = mapper_db_get_connections_by_src_dest_device_names(db, src_name, dest_name);
+
+    (*env)->ReleaseStringUTFChars(env, s1, src_name);
+    (*env)->ReleaseStringUTFChars(env, s2, dest_name);
+
+    jclass cls = (*env)->FindClass(env, "Mapper/Db/ConnectionCollection");
+    if (!cls) return 0;
+
+    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+    jobject consobj = (*env)->NewObject(env, cls, mid, jlong_ptr(cons));
+    return consobj;
 }
 
-JNIEXPORT void JNICALL Java_Mapper_Device_00024Signal_set_1instance_1allocation_1mode
-  (JNIEnv *env, jobject obj, jint mode)
+/**** Mapper.Db.Connection.Iterator ****/
+
+JNIEXPORT jlong JNICALL Java_Mapper_Db_ConnectionIterator_mdb_1deref
+(JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return;
-    msig_set_instance_allocation_mode(sig, mode);
+    void **ptr = (void**)ptr_jlong(p);
+    return jlong_ptr(*ptr);
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_instance_1allocation_1mode
-  (JNIEnv *env, jobject obj)
+JNIEXPORT jlong JNICALL Java_Mapper_Db_ConnectionIterator_mdb_1connection_1next
+(JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    return msig_get_instance_allocation_mode(sig);
+    mapper_db_connection_t **connections = (mapper_db_connection_t**)ptr_jlong(p);
+    return jlong_ptr(mapper_db_connection_next(connections));
 }
 
-JNIEXPORT jint JNICALL Java_Mapper_Device_00024Signal_num_1connections
-  (JNIEnv *env, jobject obj)
+JNIEXPORT void JNICALL Java_Mapper_Db_ConnectionIterator_mdb_1connection_1done
+(JNIEnv *env, jobject obj, jlong p)
 {
-    mapper_signal sig = get_signal_from_jobject(env, obj);
-    if (!sig) return 0;
-    return msig_num_connections(sig);
+    mapper_db_connection_t **connections = (mapper_db_connection_t**)ptr_jlong(p);
+    if (connections)
+        mapper_db_connection_done(connections);
 }
diff --git a/jni/test.java b/jni/test.java
index 36194f2..42ca7ad 100644
--- a/jni/test.java
+++ b/jni/test.java
@@ -2,10 +2,12 @@
 import Mapper.*;
 import Mapper.Device.*;
 import java.util.Arrays;
+import java.util.Iterator;
 
 class test {
     public static void main(String [] args) {
         final Device dev = new Device("javatest");
+        final Monitor mon = new Monitor(Mapper.Monitor.SUB_DEVICE_ALL);
 
         // This is how to ensure the device is freed when the program
         // exits, even on SIGINT.  The Device must be declared "final".
@@ -15,60 +17,80 @@ class test {
                 public void run()
                     {
                         dev.free();
+                        mon.free();
                     }
             });
 
-        Mapper.Device.Signal inp1 = dev.add_input("insig1", 1, 'f', "Hz",
-                                                  new PropertyValue('f', 2.0),
-                                                  null, new InputListener() {
-                public void onInput(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
-                                    float[] v,
-                                    TimeTag tt) {
-                    System.out.println("in onInput() for "
-                                       +props.name()+": "
-                                       +Arrays.toString(v));
-                }});
+        mon.Db.addDeviceCallback(new Mapper.Db.DeviceListener() {
+            public void onEvent(Mapper.Db.Device d, int event) {
+                System.out.println("db onEvent() for device "+d.name());
+            }});
+
+        mon.Db.addSignalCallback(new Mapper.Db.SignalListener() {
+            public void onEvent(Mapper.Db.Signal s, int event) {
+                System.out.println("db onEvent() for signal "+s.name());
+            }});
+
+        mon.Db.addLinkCallback(new Mapper.Db.LinkListener() {
+            public void onEvent(Mapper.Db.Link l, int event) {
+                System.out.println("db onEvent() for link "
+                                   +l.srcName()+" -> "+l.destName());
+            }});
+
+        mon.Db.addConnectionCallback(new Mapper.Db.ConnectionListener() {
+            public void onEvent(Mapper.Db.Connection c, int event) {
+                System.out.println("db onEvent() for connection "
+                                   +c.srcName+" -> "+c.destName+" @expr "
+                                   +c.expression);
+            }});
+
+        Mapper.Device.Signal inp1 = dev.addInput("insig1", 1, 'f', "Hz",
+                                                 new PropertyValue('f', 2.0),
+                                                 null, new InputListener() {
+            public void onInput(Mapper.Device.Signal sig,
+                                int instanceId,
+                                float[] v,
+                                TimeTag tt) {
+                System.out.println(" >> in onInput() for "+sig.name()+": "
+                                   +Arrays.toString(v));
+            }});
 
         System.out.println("Input signal name: "+inp1.name());
 
-        Signal out1 = dev.add_output("outsig1", 1, 'i', "Hz",
-                                     new PropertyValue('i', 0.0),
-                                     new PropertyValue('i', 1.0));
-        Signal out2 = dev.add_output("outsig2", 1, 'f', "Hz",
-                                     new PropertyValue(0.0f),
-                                     new PropertyValue(1.0f));
+        Signal out1 = dev.addOutput("outsig1", 1, 'i', "Hz",
+                                    new PropertyValue('i', 0.0),
+                                    new PropertyValue('i', 1.0));
+        Signal out2 = dev.addOutput("outsig2", 1, 'f', "Hz",
+                                    new PropertyValue(0.0f),
+                                    new PropertyValue(1.0f));
 
         System.out.println("Output signal index: "+out1.index());
-        System.out.println("Zeroeth output signal name: "+dev.get_output_by_index(0).name());
+        System.out.println("Zeroeth output signal name: "+dev.getOutput(0).name());
 
-        dev.set_property("width", new PropertyValue(256));
-        dev.set_property("height", new PropertyValue(12.5));
-        dev.set_property("depth", new PropertyValue("67"));
-        dev.set_property("deletethis", new PropertyValue("should not see me"));
-        dev.remove_property("deletethis");
+        dev.setProperty("width", new PropertyValue(256));
+        dev.setProperty("height", new PropertyValue(12.5));
+        dev.setProperty("depth", new PropertyValue("67"));
+        dev.setProperty("deletethis", new PropertyValue("should not see me"));
+        dev.removeProperty("deletethis");
 
-        out1.set_property("width", new PropertyValue(new int[] {10, 11, 12}));
-        out1.set_property("height", new PropertyValue(6.25));
-        out1.set_property("depth", new PropertyValue(new String[]{"one","two"}));
-        out1.set_property("deletethis", new PropertyValue("or me"));
-        out1.remove_property("deletethis");
-        out1.set_minimum(new PropertyValue(12));
+        out1.setProperty("width", new PropertyValue(new int[] {10, 11, 12}));
+        out1.setProperty("height", new PropertyValue(6.25));
+        out1.setProperty("depth", new PropertyValue(new String[]{"one","two"}));
+        out1.setProperty("deletethis", new PropertyValue("or me"));
+        out1.removeProperty("deletethis");
+        out1.setMinimum(new PropertyValue(12));
 
         System.out.println("Signal properties:");
-        System.out.println("  Setting name of out1 to `/out1test'.");
-        out1.properties().set_name("/out1test");
         System.out.println("  Name of out1: " + out1.properties().name());
 
         System.out.println("  Looking up `height': "
-                           + out1.properties().property_lookup("height"));
+                           + out1.properties().property("height"));
         System.out.println("  Looking up `width': "
-                           + out1.properties().property_lookup("width"));
+                           + out1.properties().property("width"));
         System.out.println("  Looking up `depth': "
-                           + out1.properties().property_lookup("depth"));
+                           + out1.properties().property("depth"));
         System.out.println("  Looking up `deletethis': "
-                           + out1.properties().property_lookup("deletethis")
+                           + out1.properties().property("deletethis")
                            + " (should be null)");
         System.out.println("  Looking up minimum: "
                            + out1.properties().minimum());
@@ -87,7 +109,20 @@ class test {
         System.out.println("Device interface: "+dev.iface());
         System.out.println("Device ip4: "+dev.ip4());
 
-        int i = 100;
+        mon.link(dev.name(), dev.name(), null);
+        while (dev.numLinksIn() <= 0) { dev.poll(100); }
+
+        Mapper.Db.Connection c = new Mapper.Db.Connection();
+        c.mode = Mapper.Db.Connection.MO_EXPRESSION;
+        c.expression = "y=x*100";
+        c.srcMin = new PropertyValue(15);
+        c.srcMax = new PropertyValue(-15);
+        c.destMax = new PropertyValue(1000);
+        c.destMin = new PropertyValue(-2000);
+        mon.connect(dev.name()+out1.name(), dev.name()+inp1.name(), c);
+        while ((dev.numConnectionsIn()) <= 0) { dev.poll(100); }
+
+        int i = 0;
         double [] ar = new double [] {0};
         TimeTag tt = new TimeTag(0,0);
 
@@ -101,71 +136,66 @@ class test {
         out1.update(new int []{i}, TimeTag.NOW);
 
         // Test instances
-        out1.set_instance_event_callback(new InstanceEventListener() {
+        out1.setInstanceEventCallback(new InstanceEventListener() {
                 public void onEvent(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
-                                    int event)
+                                    int instanceId,
+                                    int event,
+                                    TimeTag tt)
                     {
                         System.out.println("Instance "
-                                           + instance_id
+                                           + instanceId
                                            + " event " + event);
                     }
             }, InstanceEventListener.IN_ALL);
 
         System.out.println(inp1.name() + " allocation mode: "
-                           + inp1.instance_allocation_mode());
-        inp1.set_instance_allocation_mode(Device.Signal.IN_STEAL_NEWEST);
+                           + inp1.instanceAllocationMode());
+        inp1.setInstanceAllocationMode(Device.Signal.IN_STEAL_NEWEST);
         System.out.println(inp1.name() + " allocation mode: "
-                           + inp1.instance_allocation_mode());
-
-        out1.reserve_instances(new int[]{10, 11, 12});
-        out1.update_instance(10, -8);
-        out1.update_instance(10, new int[]{-8});
-        out1.instance_value(10, new int[]{0});
-        out1.release_instance(10);
-
-        out2.reserve_instances(3);
-        out2.update_instance(0, 14.2f);
-        out2.update_instance(1, new float[]{21.9f});
-        out2.instance_value(1, new float[]{0});
-        out2.update_instance(0, 12.3);
-        out2.update_instance(1, new double[]{48.12});
-        out2.instance_value(1, new double[]{0});
-        out2.release_instance(1);
-
-        inp1.reserve_instances(3, new InputListener() {
+                           + inp1.instanceAllocationMode());
+
+        out1.reserveInstances(new int[]{10, 11, 12});
+        out1.updateInstance(10, new int[]{-8});
+        out1.instanceValue(10, new int[]{0});
+        out1.releaseInstance(10);
+
+        out2.reserveInstances(3);
+        out2.updateInstance(1, new float[]{21.9f});
+        out2.instanceValue(1, new float[]{0});
+        out2.updateInstance(1, new double[]{48.12});
+        out2.instanceValue(1, new double[]{0});
+        out2.releaseInstance(1);
+
+        inp1.reserveInstances(3, new InputListener() {
                 public void onInput(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
+                                    int instanceId,
                                     float[] v,
                                     TimeTag tt) {
                     System.out.println("in onInput() for "
-                                       +props.name()+" instance "
-                                       +instance_id+": "
+                                       +sig.name()+" instance "
+                                       +instanceId+": "
                                        +Arrays.toString(v));
                 }});
         System.out.println(inp1.name() + " instance 1 cb is "
-                           + inp1.get_instance_callback(1));
-        inp1.set_instance_callback(1, new InputListener() {
+                           + inp1.getInstanceCallback(1));
+        inp1.setInstanceCallback(1, new InputListener() {
                 public void onInput(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
+                                    int instanceId,
                                     float[] v,
                                     TimeTag tt) {
                     System.out.println("in onInput() for "
-                                       +props.name()+" instance 1: "
+                                       +sig.name()+" instance 1: "
                                        +Arrays.toString(v));
                 }});
         System.out.println(inp1.name() + " instance 1 cb is "
-                           + inp1.get_instance_callback(1));
-        inp1.set_instance_callback(1, null);
+                           + inp1.getInstanceCallback(1));
+        inp1.setInstanceCallback(1, null);
         System.out.println(inp1.name() + " instance 1 cb is "
-                           + inp1.get_instance_callback(1));
+                           + inp1.getInstanceCallback(1));
 
-        out1.update(i);
-        while (i >= 0) {
+        while (i <= 100) {
             System.out.print("Updated value to: " + i);
+            out1.update(i);
 
             // Note, we are testing an implicit cast from int to float
             // here because we are passing a double[] into
@@ -175,22 +205,52 @@ class test {
             else
                 System.out.print("  Signal has no value.");
 
-            System.out.print("      \r");
+            if (i == 50) {
+                Mapper.Db.Connection mod = new Mapper.Db.Connection();
+                mod.expression = "y=x*-100";
+                System.out.println("Should be connecting "+dev.name()+out1.name()+" -> "+dev.name()+inp1.name());
+                mon.modifyConnection(dev.name()+out1.name(),
+                                     dev.name()+inp1.name(),
+                                     mod);
+            }
 
-            dev.poll(100);
-            --i;
+            dev.poll(50);
+            mon.poll(50);
+            i++;
+        }
 
-            out1.update(i);
+        // check monitor.db records
+        System.out.println("Db records:");
+
+        Iterator<Mapper.Db.Device> devs = mon.Db.devices().iterator();
+        while (devs.hasNext()) {
+            System.out.println("  device: " + devs.next().name());
+        }
+
+        // another iterator style
+        Mapper.Db.SignalCollection ins = mon.Db.inputs();
+        for (Mapper.Db.Signal s : ins) {
+            System.out.println("  signal: " + s.name());
+        }
+
+        Mapper.Db.LinkCollection links = mon.Db.links();
+        for (Mapper.Db.Link l : links) {
+            System.out.println("  link: "+ l.srcName() + " -> " + l.destName());
+        }
+
+        Mapper.Db.ConnectionCollection cons = mon.Db.connections();
+        for (Mapper.Db.Connection cc : cons) {
+            System.out.println("  connection: "+ cc.srcName + " -> " + cc.destName);
         }
 
         System.out.println();
         System.out.println("Number of connections from "
-                           + out1.name() + ": " + out1.num_connections());
+                           + out1.name() + ": " + out1.numConnections());
 
         System.out.println(inp1.name() + " oldest instance is "
-                           + inp1.oldest_active_instance());
+                           + inp1.oldestActiveInstance());
         System.out.println(inp1.name() + " newest instance is "
-                           + inp1.newest_active_instance());
+                           + inp1.newestActiveInstance());
 
         dev.free();
     }
diff --git a/jni/testquery.java b/jni/testquery.java
index 5ecfff7..798b966 100644
--- a/jni/testquery.java
+++ b/jni/testquery.java
@@ -6,6 +6,7 @@ import java.util.Arrays;
 class testquery {
     public static void main(String [] args) {
         final Device dev = new Device("javatestquery");
+        final Monitor mon = new Monitor();
 
         // This is how to ensure the device is freed when the program
         // exits, even on SIGINT.  The Device must be declared "final".
@@ -15,28 +16,27 @@ class testquery {
                 public void run()
                     {
                         dev.free();
+                        mon.free();
                     }
             });
 
-        Mapper.Device.Signal inp1 = dev.add_input("insig1", 1, 'f', "Hz", null,
-                                                  null, new InputListener() {
+        Mapper.Device.Signal inp1 = dev.addInput("insig1", 1, 'f', "Hz", null,
+                                                 null, new InputListener() {
             public void onInput(Mapper.Device.Signal sig,
-                                Mapper.Db.Signal props,
-                                int instance_id,
+                                int instanceId,
                                 float[] v,
                                 TimeTag tt) {
                     System.out.println("in onInput(): "+Arrays.toString(v));
                 }});
 
-        Signal out1 = dev.add_output("outsig1", 1, 'i', "Hz", null, null);
-        out1.set_callback(
+        Signal out1 = dev.addOutput("outsig1", 1, 'i', "Hz", null, null);
+        out1.setCallback(
             new InputListener() {
             public void onInput(Mapper.Device.Signal sig,
-                                Mapper.Db.Signal props,
-                                int instance_id,
+                                int instanceId,
                                 int[] v,
                                 TimeTag tt) {
-                    System.out.println("in onQueryResponse(): "+Arrays.toString(v));
+                    System.out.println("  >> in onQueryResponse(): "+Arrays.toString(v));
                 }});
 
         System.out.println("Waiting for ready...");
@@ -51,13 +51,20 @@ class testquery {
         System.out.println("Device interface: "+dev.iface());
         System.out.println("Device ip4: "+dev.ip4());
 
+        mon.link(dev.name(), dev.name(), null);
+        while (dev.numLinksIn() <= 0) { dev.poll(100); }
+
+        mon.connect(dev.name()+out1.name(), dev.name()+inp1.name(), null);
+        while ((dev.numConnectionsIn()) <= 0) { dev.poll(100); }
+
         int i = 100;
-        while (--i >= 0) {
-            dev.poll(100);
+        while (i >= 0) {
             inp1.update(new float[] {i});
-            System.out.println("  Updated input value: " + i);
+            System.out.println("\nUpdating input value to [" + i + "]");
             System.out.println("  Querying...");
-            out1.query_remotes();
+            out1.queryRemotes();
+            dev.poll(100);
+            --i;
         }
         dev.free();
     }
diff --git a/jni/testreverse.java b/jni/testreverse.java
index 5b0c65f..3878869 100644
--- a/jni/testreverse.java
+++ b/jni/testreverse.java
@@ -6,6 +6,7 @@ import java.util.Arrays;
 class testreverse {
     public static void main(String [] args) {
         final Device dev = new Device("javatestreverse");
+        final Monitor mon = new Monitor();
 
         // This is how to ensure the device is freed when the program
         // exits, even on SIGINT.  The Device must be declared "final".
@@ -15,28 +16,27 @@ class testreverse {
                 public void run()
                     {
                         dev.free();
+                        mon.free();
                     }
             });
 
-        Mapper.Device.Signal inp1 = dev.add_input("insig1", 1, 'f', "Hz", null,
-                                                  null, new InputListener() {
+        Mapper.Device.Signal inp1 = dev.addInput("insig1", 1, 'f', "Hz", null,
+                                                 null, new InputListener() {
                 public void onInput(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
+                                    int instanceId,
                                     float[] v,
                                     TimeTag tt) {
                     System.out.println("in onInput(): "+Arrays.toString(v));
                 }});
 
-        Signal out1 = dev.add_output("outsig1", 1, 'i', "Hz", null, null);
-        out1.set_callback(
+        Signal out1 = dev.addOutput("outsig1", 1, 'i', "Hz", null, null);
+        out1.setCallback(
             new InputListener() {
                 public void onInput(Mapper.Device.Signal sig,
-                                    Mapper.Db.Signal props,
-                                    int instance_id,
+                                    int instanceId,
                                     int[] v,
                                     TimeTag tt) {
-                    System.out.println("in onOutput(): "+Arrays.toString(v));
+                    System.out.println("  >> in onInput(): "+Arrays.toString(v));
                 }});
 
         System.out.println("Waiting for ready...");
@@ -51,11 +51,20 @@ class testreverse {
         System.out.println("Device interface: "+dev.iface());
         System.out.println("Device ip4: "+dev.ip4());
 
+        mon.link(dev.name(), dev.name(), null);
+        while (dev.numLinksIn() <= 0) { dev.poll(100); }
+
+        Mapper.Db.Connection c = new Mapper.Db.Connection();
+        c.mode = Mapper.Db.Connection.MO_REVERSE;
+        mon.connect(dev.name()+out1.name(), dev.name()+inp1.name(), c);
+        while ((dev.numConnectionsIn()) <= 0) { dev.poll(100); }
+
         int i = 100;
         while (i >= 0) {
+            System.out.println("\nUpdating input to ["+i+"]");
+            inp1.update(new int[] {i});
             dev.poll(100);
             --i;
-            inp1.update(new int[] {i});
         }
         dev.free();
     }
diff --git a/jni/testspeed.java b/jni/testspeed.java
new file mode 100644
index 0000000..67c9ac1
--- /dev/null
+++ b/jni/testspeed.java
@@ -0,0 +1,68 @@
+
+import Mapper.*;
+import Mapper.Device.*;
+import java.util.Arrays;
+
+class testspeed {
+    public static boolean updated = true;
+
+    public static void main(String [] args) {
+        final Device dev = new Device("javatest");
+        final Monitor mon = new Monitor(Mapper.Monitor.SUB_DEVICE_ALL);
+
+        // This is how to ensure the device is freed when the program
+        // exits, even on SIGINT.  The Device must be declared "final".
+        Runtime.getRuntime().addShutdownHook(new Thread()
+            {
+                @Override
+                public void run()
+                    {
+                        dev.free();
+                        mon.free();
+                    }
+            });
+
+        InputListener h = new InputListener() {
+            public void onInput(Mapper.Device.Signal sig,
+                                int instanceId,
+                                float[] v,
+                                TimeTag tt) {
+                testspeed.updated = true;
+            }
+        };
+
+        Mapper.Device.Signal in = dev.addInput("insig", 1, 'f', "Hz", null,
+                                               null, h);
+
+        Signal out = dev.addOutput("outsig", 1, 'i', "Hz", null, null);
+
+        System.out.println("Waiting for ready...");
+        while (!dev.ready()) {
+            dev.poll(100);
+        }
+        System.out.println("Device is ready.");
+
+        mon.link(dev.name(), dev.name(), null);
+        while (dev.numLinksIn() <= 0) { dev.poll(100); }
+
+        Mapper.Db.Connection c = new Mapper.Db.Connection();
+        mon.connect(dev.name()+out.name(), dev.name()+in.name(), null);
+        while ((dev.numConnectionsIn()) <= 0) { dev.poll(100); }
+
+        mon.free();
+
+        double then = dev.now().getDouble();
+        int i = 0;
+        while (i < 10000) {
+            if (testspeed.updated) {
+                out.update(i);
+                i++;
+                testspeed.updated = false;
+            }
+            dev.poll(1);
+        }
+        double elapsed = dev.now().getDouble() - then;
+        System.out.println("Sent "+i+" messages in "+elapsed+" seconds.");
+        dev.free();
+    }
+}
diff --git a/src/admin.c b/src/admin.c
index 8b6a9ff..78b7b92 100644
--- a/src/admin.c
+++ b/src/admin.c
@@ -8,6 +8,7 @@
 #include <stdarg.h>
 #include <sys/time.h>
 #include <zlib.h>
+#include <math.h>
 
 #ifdef HAVE_GETIFADDRS
  #include <ifaddrs.h>
@@ -29,6 +30,12 @@
 #include "config.h"
 #include <mapper/mapper.h>
 
+// set to 1 to force mesh comms to use admin bus instead for debugging
+#define FORCE_ADMIN_TO_BUS      0
+
+#define BUNDLE_DEST_SUBSCRIBERS (void*)-1
+#define BUNDLE_DEST_BUS         0
+
 /*! Internal function to get the current time. */
 static double get_current_time()
 {
@@ -41,6 +48,21 @@ static double get_current_time()
 #endif
 }
 
+/* Note: any call to liblo where get_liblo_error will be called
+ * afterwards must lock this mutex, otherwise there is a race
+ * condition on receiving this information.  Could be fixed by the
+ * liblo error handler having a user context pointer. */
+static int liblo_error_num = 0;
+static void liblo_error_handler(int num, const char *msg, const char *path)
+{
+    liblo_error_num = num;
+    if (num == LO_NOPORT) {
+        trace("liblo could not start a server because port unavailable\n");
+    } else
+        fprintf(stderr, "[libmapper] liblo server error %d in path %s: %s\n",
+                num, path, msg);
+}
+
 const char* admin_msg_strings[] =
 {
     "/connect",                 /* ADM_CONNECT */
@@ -50,22 +72,19 @@ const char* admin_msg_strings[] =
     "/device",                  /* ADM_DEVICE */
     "/disconnect",              /* ADM_DISCONNECT */
     "/disconnected",            /* ADM_DISCONNECTED */
-    "%s/connections/get",       /* ADM_GET_MY_CONNECTIONS */
-    "%s/connections/in/get",    /* ADM_GET_MY_CONNECTIONS_IN */
-    "%s/connections/out/get",   /* ADM_GET_MY_CONNECTIONS_OUT */
-    "%s/info/get",              /* ADM_GET_MY_DEVICE */
-    "%s/links/get",             /* ADM_GET_MY_LINKS */
-    "%s/links/in/get",          /* ADM_GET_MY_LINKS_IN */
-    "%s/links/out/get",         /* ADM_GET_MY_LINKS_OUT */
-    "%s/signals/get",           /* ADM_GET_MY_SIGNALS */
-    "%s/signals/input/get",     /* ADM_GET_MY_SIGNALS_IN */
-    "%s/signals/output/get",    /* ADM_GET_MY_SIGNALS_OUT */
     "/link",                    /* ADM_LINK */
     "/link/modify",             /* ADM_LINK_MODIFY */
     "/linkTo",                  /* ADM_LINK_TO */
     "/linked",                  /* ADM_LINKED */
+    "/link/ping",               /* ADM_LINK_PING */
     "/logout",                  /* ADM_LOGOUT */
     "/signal",                  /* ADM_SIGNAL */
+    "/input",                   /* ADM_INPUT */
+    "/output",                  /* ADM_OUTPUT */
+    "/input/removed",           /* ADM_INPUT_SIGNAL_REMOVED */
+    "/output/removed",          /* ADM_OUTPUT_SIGNAL_REMOVED */
+    "%s/subscribe",             /* ADM_SUBSCRIBE */
+    "%s/unsubscribe",           /* ADM_UNSUBSCRIBE */
     "/sync",                    /* ADM_SYNC */
     "/unlink",                  /* ADM_UNLINK */
     "/unlinked",                /* ADM_UNLINKED */
@@ -87,16 +106,16 @@ static int handler_device(const char *, const char *, lo_arg **,
                           int, lo_message, void *);
 static int handler_logout(const char *, const char *, lo_arg **,
                           int, lo_message, void *);
-static int handler_id_n_signals_input_get(const char *, const char *,
-                                          lo_arg **, int, lo_message,
-                                          void *);
-static int handler_id_n_signals_output_get(const char *, const char *,
-                                           lo_arg **, int, lo_message,
-                                           void *);
-static int handler_id_n_signals_get(const char *, const char *,
-                                    lo_arg **, int, lo_message, void *);
+static int handler_device_subscribe(const char *, const char *, lo_arg **,
+                                    int, lo_message, void *);
+static int handler_device_unsubscribe(const char *, const char *, lo_arg **,
+                                      int, lo_message, void *);
 static int handler_signal_info(const char *, const char *, lo_arg **,
                                int, lo_message, void *);
+static int handler_input_signal_removed(const char *, const char *, lo_arg **,
+                                        int, lo_message, void *);
+static int handler_output_signal_removed(const char *, const char *, lo_arg **,
+                                         int, lo_message, void *);
 static int handler_device_name_probe(const char *, const char *, lo_arg **,
                                      int, lo_message, void *);
 static int handler_device_name_registered(const char *, const char *, lo_arg **,
@@ -109,16 +128,12 @@ static int handler_device_linked(const char *, const char *, lo_arg **,
                                  int, lo_message, void *);
 static int handler_device_link_modify(const char *, const char *, lo_arg **, int,
                                       lo_message, void *);
+static int handler_device_link_ping(const char *, const char *, lo_arg **, int,
+                                    lo_message, void *);
 static int handler_device_unlink(const char *, const char *, lo_arg **,
                                  int, lo_message, void *);
 static int handler_device_unlinked(const char *, const char *, lo_arg **,
                                    int, lo_message, void *);
-static int handler_device_links_get(const char *, const char *, lo_arg **,
-                                    int, lo_message, void *);
-static int handler_device_links_in_get(const char *, const char *, lo_arg **,
-                                       int, lo_message, void *);
-static int handler_device_links_out_get(const char *, const char *, lo_arg **,
-                                        int, lo_message, void *);
 static int handler_signal_connect(const char *, const char *, lo_arg **,
                                   int, lo_message, void *);
 static int handler_signal_connectTo(const char *, const char *, lo_arg **,
@@ -131,12 +146,6 @@ static int handler_signal_disconnect(const char *, const char *, lo_arg **,
                                      int, lo_message, void *);
 static int handler_signal_disconnected(const char *, const char *, lo_arg **,
                                        int, lo_message, void *);
-static int handler_device_connections_get(const char *, const char *,
-                                          lo_arg **, int, lo_message, void *);
-static int handler_device_connections_in_get(const char *, const char *,
-                                             lo_arg **, int, lo_message, void *);
-static int handler_device_connections_out_get(const char *, const char *,
-                                              lo_arg **, int, lo_message, void *);
 static int handler_sync(const char *, const char *,
                         lo_arg **, int, lo_message, void *);
 
@@ -146,48 +155,68 @@ struct handler_method_assoc {
     char *types;
     lo_method_handler h;
 };
-static struct handler_method_assoc device_handlers[] = {
-    {ADM_WHO,                   "",         handler_who},
-    {ADM_GET_MY_SIGNALS,        NULL,       handler_id_n_signals_get},
-    {ADM_GET_MY_SIGNALS_IN,     NULL,       handler_id_n_signals_input_get},
-    {ADM_GET_MY_SIGNALS_OUT,    NULL,       handler_id_n_signals_output_get},
-    {ADM_GET_MY_DEVICE,         "",         handler_who},
-    {ADM_GET_MY_LINKS,          "",         handler_device_links_get},
-    {ADM_GET_MY_LINKS_IN,       "",         handler_device_links_in_get},
-    {ADM_GET_MY_LINKS_OUT,      "",         handler_device_links_out_get},
+
+// handlers needed by both "devices" and "monitors"
+static struct handler_method_assoc admin_bus_handlers[] = {
+    {ADM_LOGOUT,                 NULL,      handler_logout},
+};
+const int N_ADMIN_BUS_HANDLERS =
+    sizeof(admin_bus_handlers)/sizeof(admin_bus_handlers[0]);
+
+static struct handler_method_assoc admin_mesh_handlers[] = {
+    {ADM_LINKED,                 NULL,      handler_device_linked},
+    {ADM_UNLINKED,               NULL,      handler_device_unlinked},
+    {ADM_CONNECTED,              NULL,      handler_signal_connected},
+    {ADM_DISCONNECTED,           "ss",      handler_signal_disconnected},
+};
+const int N_ADMIN_MESH_HANDLERS =
+    sizeof(admin_mesh_handlers)/sizeof(admin_mesh_handlers[0]);
+
+static struct handler_method_assoc device_bus_handlers[] = {
+    {ADM_WHO,                   NULL,       handler_who},
     {ADM_LINK,                  NULL,       handler_device_link},
     {ADM_LINK_TO,               NULL,       handler_device_linkTo},
     {ADM_LINKED,                NULL,       handler_device_linked},
     {ADM_LINK_MODIFY,           NULL,       handler_device_link_modify},
     {ADM_UNLINK,                NULL,       handler_device_unlink},
     {ADM_UNLINKED,              NULL,       handler_device_unlinked},
-    {ADM_GET_MY_CONNECTIONS,    NULL,       handler_device_connections_get},
-    {ADM_GET_MY_CONNECTIONS_IN, NULL,       handler_device_connections_in_get},
-    {ADM_GET_MY_CONNECTIONS_OUT, NULL,       handler_device_connections_out_get},
+    {ADM_SUBSCRIBE,             NULL,       handler_device_subscribe},
+    {ADM_UNSUBSCRIBE,           NULL,       handler_device_unsubscribe},
     {ADM_CONNECT,               NULL,       handler_signal_connect},
     {ADM_CONNECT_TO,            NULL,       handler_signal_connectTo},
     {ADM_CONNECTED,             NULL,       handler_signal_connected},
     {ADM_CONNECTION_MODIFY,     NULL,       handler_signal_connection_modify},
     {ADM_DISCONNECT,            "ss",       handler_signal_disconnect},
     {ADM_DISCONNECTED,          "ss",       handler_signal_disconnected},
-    {ADM_LOGOUT,                NULL,       handler_logout},
-    {ADM_SYNC,                  "iifiid",   handler_sync},
 };
-const int N_DEVICE_HANDLERS =
-    sizeof(device_handlers)/sizeof(device_handlers[0]);
+const int N_DEVICE_BUS_HANDLERS =
+    sizeof(device_bus_handlers)/sizeof(device_bus_handlers[0]);
+
+static struct handler_method_assoc device_mesh_handlers[] = {
+    {ADM_SUBSCRIBE,             NULL,       handler_device_subscribe},
+    {ADM_UNSUBSCRIBE,           NULL,       handler_device_unsubscribe},
+    {ADM_CONNECT_TO,            NULL,       handler_signal_connectTo},
+    {ADM_LINK_PING,             "iiid",     handler_device_link_ping},
+};
+
+const int N_DEVICE_MESH_HANDLERS =
+    sizeof(device_mesh_handlers)/sizeof(device_mesh_handlers[0]);
+
+static struct handler_method_assoc monitor_bus_handlers[] = {
+    {ADM_DEVICE,                NULL,       handler_device},
+    {ADM_SYNC,                  NULL,       handler_sync},
+};
+const int N_MONITOR_BUS_HANDLERS =
+    sizeof(monitor_bus_handlers)/sizeof(monitor_bus_handlers[0]);
 
-static struct handler_method_assoc monitor_handlers[] = {
+static struct handler_method_assoc monitor_mesh_handlers[] = {
     {ADM_DEVICE,                NULL,       handler_device},
-    {ADM_LOGOUT,                NULL,       handler_logout},
     {ADM_SIGNAL,                NULL,       handler_signal_info},
-    {ADM_LINKED,                NULL,       handler_device_linked},
-    {ADM_UNLINKED,              NULL,       handler_device_unlinked},
-    {ADM_CONNECTED,             NULL,       handler_signal_connected},
-    {ADM_DISCONNECTED,          "ss",       handler_signal_disconnected},
-    {ADM_SYNC,                  "iifiid",   handler_sync},
+    {ADM_INPUT_REMOVED,         "s",        handler_input_signal_removed},
+    {ADM_OUTPUT_REMOVED,        "s",        handler_output_signal_removed},
 };
-const int N_MONITOR_HANDLERS =
-    sizeof(monitor_handlers)/sizeof(monitor_handlers[0]);
+const int N_MONITOR_MESH_HANDLERS =
+    sizeof(monitor_mesh_handlers)/sizeof(monitor_mesh_handlers[0]);
 
 /* Internal LibLo error handler */
 static void handler_error(int num, const char *msg, const char *where)
@@ -345,19 +374,61 @@ static void seed_srand()
     srand(s);
 }
 
+static void mapper_admin_add_admin_methods(mapper_admin admin)
+{
+    int i;
+    for (i=0; i < N_ADMIN_BUS_HANDLERS; i++)
+    {
+        lo_server_add_method(admin->bus_server,
+                             admin_msg_strings[admin_bus_handlers[i].str_index],
+                             admin_bus_handlers[i].types,
+                             admin_bus_handlers[i].h,
+                             admin);
+    }
+    for (i=0; i < N_ADMIN_MESH_HANDLERS; i++)
+    {
+        lo_server_add_method(admin->mesh_server,
+                             admin_msg_strings[admin_mesh_handlers[i].str_index],
+                             admin_mesh_handlers[i].types,
+                             admin_mesh_handlers[i].h,
+                             admin);
+        // add them for bus also
+        lo_server_add_method(admin->bus_server,
+                             admin_msg_strings[admin_mesh_handlers[i].str_index],
+                             admin_mesh_handlers[i].types,
+                             admin_mesh_handlers[i].h,
+                             admin);
+    }
+}
+
 static void mapper_admin_add_device_methods(mapper_admin admin,
                                             mapper_device device)
 {
     int i;
     char fullpath[256];
-    for (i=0; i < N_DEVICE_HANDLERS; i++)
+    for (i=0; i < N_DEVICE_BUS_HANDLERS; i++)
+    {
+        snprintf(fullpath, 256,
+                 admin_msg_strings[device_bus_handlers[i].str_index],
+                 mdev_name(admin->device));
+        lo_server_add_method(admin->bus_server, fullpath,
+                             device_bus_handlers[i].types,
+                             device_bus_handlers[i].h,
+                             admin);
+    }
+    for (i=0; i < N_DEVICE_MESH_HANDLERS; i++)
     {
         snprintf(fullpath, 256,
-                 admin_msg_strings[device_handlers[i].str_index],
+                 admin_msg_strings[device_mesh_handlers[i].str_index],
                  mdev_name(admin->device));
-        lo_server_add_method(admin->admin_server, fullpath,
-                             device_handlers[i].types,
-                             device_handlers[i].h,
+        lo_server_add_method(admin->mesh_server, fullpath,
+                             device_mesh_handlers[i].types,
+                             device_mesh_handlers[i].h,
+                             admin);
+        // add them for bus also
+        lo_server_add_method(admin->bus_server, fullpath,
+                             device_mesh_handlers[i].types,
+                             device_mesh_handlers[i].h,
                              admin);
     }
 }
@@ -365,12 +436,26 @@ static void mapper_admin_add_device_methods(mapper_admin admin,
 static void mapper_admin_add_monitor_methods(mapper_admin admin)
 {
     int i;
-    for (i=0; i < N_MONITOR_HANDLERS; i++)
+    for (i=0; i < N_MONITOR_BUS_HANDLERS; i++)
+    {
+        lo_server_add_method(admin->bus_server,
+                             admin_msg_strings[monitor_bus_handlers[i].str_index],
+                             monitor_bus_handlers[i].types,
+                             monitor_bus_handlers[i].h,
+                             admin);
+    }
+    for (i=0; i < N_MONITOR_MESH_HANDLERS; i++)
     {
-        lo_server_add_method(admin->admin_server,
-                             admin_msg_strings[monitor_handlers[i].str_index],
-                             monitor_handlers[i].types,
-                             monitor_handlers[i].h,
+        lo_server_add_method(admin->mesh_server,
+                             admin_msg_strings[monitor_mesh_handlers[i].str_index],
+                             monitor_mesh_handlers[i].types,
+                             monitor_mesh_handlers[i].h,
+                             admin);
+        // add them for bus also
+        lo_server_add_method(admin->bus_server,
+                             admin_msg_strings[monitor_mesh_handlers[i].str_index],
+                             monitor_mesh_handlers[i].types,
+                             monitor_mesh_handlers[i].h,
                              admin);
     }
 }
@@ -378,11 +463,20 @@ static void mapper_admin_add_monitor_methods(mapper_admin admin)
 static void mapper_admin_remove_monitor_methods(mapper_admin admin)
 {
     int i;
-    for (i=0; i < N_MONITOR_HANDLERS; i++)
+    for (i=0; i < N_MONITOR_BUS_HANDLERS; i++)
+    {
+        lo_server_del_method(admin->bus_server,
+                             admin_msg_strings[monitor_bus_handlers[i].str_index],
+                             monitor_bus_handlers[i].types);
+    }
+    for (i=0; i < N_MONITOR_MESH_HANDLERS; i++)
     {
-        lo_server_del_method(admin->admin_server,
-                             admin_msg_strings[monitor_handlers[i].str_index],
-                             monitor_handlers[i].types);
+        lo_server_del_method(admin->mesh_server,
+                             admin_msg_strings[monitor_mesh_handlers[i].str_index],
+                             monitor_mesh_handlers[i].types);
+        lo_server_del_method(admin->bus_server,
+                             admin_msg_strings[monitor_mesh_handlers[i].str_index],
+                             monitor_mesh_handlers[i].types);
     }
 }
 
@@ -408,23 +502,23 @@ mapper_admin mapper_admin_new(const char *iface, const char *group, int port)
         trace("no interface found\n");
 
     /* Open address */
-    admin->admin_addr = lo_address_new(group, s_port);
-    if (!admin->admin_addr) {
+    admin->bus_addr = lo_address_new(group, s_port);
+    if (!admin->bus_addr) {
         free(admin);
         return NULL;
     }
 
     /* Set TTL for packet to 1 -> local subnet */
-    lo_address_set_ttl(admin->admin_addr, 1);
+    lo_address_set_ttl(admin->bus_addr, 1);
 
     /* Specify the interface to use for multicasting */
 #ifdef HAVE_LIBLO_SET_IFACE
-    lo_address_set_iface(admin->admin_addr,
+    lo_address_set_iface(admin->bus_addr,
                          admin->interface_name, 0);
 #endif
 
     /* Open server for multicast group 224.0.1.3, port 7570 */
-    admin->admin_server =
+    admin->bus_server =
 #ifdef HAVE_LIBLO_SERVER_IFACE
         lo_server_new_multicast_iface(group, s_port,
                                       admin->interface_name, 0,
@@ -433,14 +527,22 @@ mapper_admin mapper_admin_new(const char *iface, const char *group, int port)
         lo_server_new_multicast(group, s_port, handler_error);
 #endif
 
-    if (!admin->admin_server) {
-        lo_address_free(admin->admin_addr);
+    if (!admin->bus_server) {
+        lo_address_free(admin->bus_addr);
         free(admin);
         return NULL;
     }
 
+    // Also open address/server for mesh-style admin communications
+    // TODO: use TCP instead?
+    while (!(admin->mesh_server = lo_server_new(0, liblo_error_handler))) {}
+
     // Disable liblo message queueing.
-    lo_server_enable_queue(admin->admin_server, 0, 1);
+    lo_server_enable_queue(admin->bus_server, 0, 1);
+    lo_server_enable_queue(admin->mesh_server, 0, 1);
+
+    // Add methods required by both devices and monitors
+    mapper_admin_add_admin_methods(admin);
 
     return admin;
 }
@@ -454,30 +556,85 @@ void mapper_admin_send_bundle(mapper_admin admin)
 {
     if (!admin->bundle)
         return;
-
-    lo_send_bundle_from(admin->admin_addr,
-                        admin->device ? admin->device->server : NULL,
-                        admin->bundle);
+#if FORCE_ADMIN_TO_BUS
+    lo_send_bundle_from(admin->bus_addr, admin->mesh_server, admin->bundle);
+#else
+    if (admin->bundle_dest == BUNDLE_DEST_SUBSCRIBERS) {
+        mapper_admin_subscriber *s = &admin->subscribers;
+        if (*s) {
+            mapper_clock_now(&admin->clock, &admin->clock.now);
+        }
+        while (*s) {
+            if ((*s)->lease_expiration_sec < admin->clock.now.sec || !(*s)->flags) {
+                // subscription expired, remove from subscriber list
+                mapper_admin_subscriber temp = *s;
+                *s = temp->next;
+                if (temp->address)
+                    lo_address_free(temp->address);
+                free(temp);
+                continue;
+            }
+            if ((*s)->flags & admin->message_type) {
+                lo_send_bundle_from((*s)->address, admin->mesh_server, admin->bundle);
+            }
+            s = &(*s)->next;
+        }
+    }
+    else if (admin->bundle_dest == BUNDLE_DEST_BUS) {
+        lo_send_bundle_from(admin->bus_addr, admin->mesh_server, admin->bundle);
+    }
+    else {
+        lo_send_bundle_from(admin->bundle_dest, admin->mesh_server, admin->bundle);
+    }
+#endif
     lo_bundle_free_messages(admin->bundle);
     admin->bundle = 0;
 }
 
-static int mapper_admin_check_bundle(mapper_admin admin)
+static int mapper_admin_init_bundle(mapper_admin admin)
 {
-    if (admin->bundle && lo_bundle_count(admin->bundle) >= 20) {
+    if (admin->bundle)
         mapper_admin_send_bundle(admin);
-    }
+
+    mapper_clock_now(&admin->clock, &admin->clock.now);
+    admin->bundle = lo_bundle_new(admin->clock.now);
     if (!admin->bundle) {
-        mapper_clock_now(&admin->clock, &admin->clock.now);
-        admin->bundle = lo_bundle_new(admin->clock.now);
-        if (!admin->bundle) {
-            trace("couldn't allocate lo_bundle\n");
-            return 1;
-        }
+        trace("couldn't allocate lo_bundle\n");
+        return 1;
     }
+
     return 0;
 }
 
+void mapper_admin_set_bundle_dest_bus(mapper_admin admin)
+{
+    if (admin->bundle && admin->bundle_dest != BUNDLE_DEST_BUS)
+        mapper_admin_send_bundle(admin);
+    admin->bundle_dest = 0;
+    if (!admin->bundle)
+        mapper_admin_init_bundle(admin);
+}
+
+void mapper_admin_set_bundle_dest_mesh(mapper_admin admin, lo_address address)
+{
+    if (admin->bundle && admin->bundle_dest != address)
+        mapper_admin_send_bundle(admin);
+    admin->bundle_dest = address;
+    if (!admin->bundle)
+        mapper_admin_init_bundle(admin);
+}
+
+void mapper_admin_set_bundle_dest_subscribers(mapper_admin admin, int type)
+{
+    if ((admin->bundle && admin->bundle_dest != BUNDLE_DEST_SUBSCRIBERS) ||
+        (admin->message_type != type))
+        mapper_admin_send_bundle(admin);
+    admin->bundle_dest = BUNDLE_DEST_SUBSCRIBERS;
+    admin->message_type = type;
+    if (!admin->bundle)
+        mapper_admin_init_bundle(admin);
+}
+
 /*! Free the memory allocated by a mapper admin structure.
  *  \param admin An admin structure handle.
  */
@@ -492,17 +649,30 @@ void mapper_admin_free(mapper_admin admin)
     if (admin->interface_name)
         free(admin->interface_name);
 
-    if (admin->admin_server)
-        lo_server_free(admin->admin_server);
+    if (admin->bus_server)
+        lo_server_free(admin->bus_server);
+
+    if (admin->mesh_server)
+        lo_server_free(admin->mesh_server);
 
-    if (admin->admin_addr)
-        lo_address_free(admin->admin_addr);
+    if (admin->bus_addr)
+        lo_address_free(admin->bus_addr);
+
+    mapper_admin_subscriber s;
+    while (admin->subscribers) {
+        s = admin->subscribers;
+        if (s->address)
+            lo_address_free(s->address);
+        admin->subscribers = s->next;
+        free(s);
+    }
 
     free(admin);
 }
 
 /*! Probe the admin bus to see if a device's proposed name.ordinal is
- *  already taken. */
+ *  already taken.
+ */
 static void mapper_admin_probe_device_name(mapper_admin admin,
                                            mapper_device device)
 {
@@ -521,7 +691,7 @@ static void mapper_admin_probe_device_name(mapper_admin admin,
 
     /* For the same reason, we can't use mapper_admin_send()
      * here. */
-    lo_send(admin->admin_addr, "/name/probe", "si",
+    lo_send(admin->bus_addr, "/name/probe", "si",
             name, admin->random_id);
 }
 
@@ -545,9 +715,9 @@ void mapper_admin_add_device(mapper_admin admin, mapper_device dev)
         /* Add methods for admin bus.  Only add methods needed for
          * allocation here. Further methods are added when the device is
          * registered. */
-        lo_server_add_method(admin->admin_server, "/name/probe", NULL,
+        lo_server_add_method(admin->bus_server, "/name/probe", NULL,
                              handler_device_name_probe, admin);
-        lo_server_add_method(admin->admin_server, "/name/registered", NULL,
+        lo_server_add_method(admin->bus_server, "/name/registered", NULL,
                              handler_device_name_registered, admin);
 
         /* Probe potential name to admin bus. */
@@ -573,6 +743,160 @@ void mapper_admin_remove_monitor(mapper_admin admin, mapper_monitor mon)
     }
 }
 
+static void mapper_admin_maybe_send_ping(mapper_admin admin, int force)
+{
+    mapper_device md = admin->device;
+    int go = 0;
+
+    mapper_clock_t *clock = &admin->clock;
+    mapper_clock_now(clock, &clock->now);
+    if (force || (clock->now.sec >= clock->next_ping)) {
+        go = 1;
+        clock->next_ping = clock->now.sec + 5 + (rand() % 4);
+    }
+
+    if (!md || !go)
+        return;
+
+    mapper_admin_set_bundle_dest_bus(admin);
+    mapper_admin_bundle_message(admin, ADM_SYNC, 0, "si", mdev_name(md),
+                                md->props.version);
+
+    int elapsed;
+    // some housekeeping: periodically check if our links are still active
+    mapper_router router = md->routers;
+    while (router) {
+        if (router->props.dest_name_hash == md->props.name_hash) {
+            // don't bother sending pings to self
+            router = router->next;
+            continue;
+        }
+        mapper_sync_clock sync = &router->clock;
+        elapsed = (sync->response.timetag.sec
+                   ? clock->now.sec - sync->response.timetag.sec : 0);
+        if (md->link_timeout_sec && elapsed > md->link_timeout_sec) {
+            if (sync->response.message_id > 0) {
+                trace("<%s> Lost contact with linked device %s "
+                      "(%d seconds since sync).\n", mdev_name(md),
+                      router->props.dest_name, elapsed);
+                // tentatively mark link as expired
+                sync->response.message_id = -1;
+                sync->response.timetag.sec = clock->now.sec;
+            }
+            else {
+                trace("<%s> Removing link to unresponsive device %s "
+                      "(%d seconds since warning).\n", mdev_name(md),
+                      router->props.dest_name, elapsed);
+                // Call the local link handler if it exists
+                if (md->link_cb)
+                    md->link_cb(md, &router->props, MDEV_LOCAL_DESTROYED,
+                                md->link_cb_userdata);
+
+                // inform subscribers of link timeout
+                mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_OUT);
+                mapper_admin_bundle_message(admin, ADM_UNLINKED, 0, "ssss",
+                                            mdev_name(md),
+                                            router->props.dest_name,
+                                            "@status", "timeout");
+
+                // remove related data structures
+                mdev_remove_router(md, router);
+            }
+        }
+        else {
+            lo_bundle b = lo_bundle_new(clock->now);
+            lo_message m = lo_message_new();
+            lo_message_add_int32(m, mdev_id(md));
+            ++sync->sent.message_id;
+            if (sync->sent.message_id < 0)
+                sync->sent.message_id = 0;
+            lo_message_add_int32(m, sync->sent.message_id);
+            lo_message_add_int32(m, sync->response.message_id);
+            if (sync->response.timetag.sec)
+                lo_message_add_double(m, mapper_timetag_difference(clock->now,
+                                                                   sync->response.timetag));
+            else
+                lo_message_add_double(m, 0.);
+            // need to send immediately
+            lo_bundle_add_message(b, admin_msg_strings[ADM_LINK_PING], m);
+#if FORCE_ADMIN_TO_BUS
+            lo_send_bundle_from(admin->bus_addr, admin->mesh_server, b);
+#else
+            lo_send_bundle_from(router->admin_addr, admin->mesh_server, b);
+#endif
+            mapper_timetag_cpy(&sync->sent.timetag, lo_bundle_get_timestamp(b));
+            lo_bundle_free_messages(b);
+        }
+        router = router->next;
+    }
+
+    mapper_receiver receiver = md->receivers;
+    while (receiver) {
+        if (receiver->props.src_name_hash == md->props.name_hash) {
+            // don't bother sending pings to self
+            receiver = receiver->next;
+            continue;
+        }
+        mapper_sync_clock sync = &receiver->clock;
+        elapsed = (sync->response.timetag.sec
+                   ? clock->now.sec - sync->response.timetag.sec : 0);
+        if (md->link_timeout_sec && elapsed > md->link_timeout_sec) {
+            if (sync->response.message_id > 0) {
+                trace("<%s> Lost contact with linked device %s "
+                      "(%d seconds since sync).\n", mdev_name(md),
+                      receiver->props.src_name, elapsed);
+                // tentatively mark link as expired
+                sync->response.message_id = -1;
+                sync->response.timetag.sec = clock->now.sec;
+            }
+            else {
+                trace("<%s> Removing link from unresponsive device %s "
+                      "(%d seconds since warning).\n", mdev_name(md),
+                      receiver->props.dest_name, elapsed);
+                // Call the local link handler if it exists
+                if (md->link_cb)
+                    md->link_cb(md, &receiver->props, MDEV_LOCAL_DESTROYED,
+                                md->link_cb_userdata);
+
+                // inform subscribers of link timeout
+                mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_IN);
+                mapper_admin_bundle_message(admin, ADM_UNLINKED, 0, "ssss",
+                                            receiver->props.src_name,
+                                            mdev_name(md),
+                                            "@status", "timeout");
+
+                // remove related data structures
+                mdev_remove_receiver(md, receiver);
+            }
+        }
+        else {
+            lo_bundle b = lo_bundle_new(clock->now);
+            lo_message m = lo_message_new();
+            lo_message_add_int32(m, mdev_id(md));
+            ++sync->sent.message_id;
+            if (sync->sent.message_id < 0)
+                sync->sent.message_id = 0;
+            lo_message_add_int32(m, sync->sent.message_id);
+            lo_message_add_int32(m, sync->response.message_id);
+            if (sync->response.timetag.sec)
+                lo_message_add_double(m, mapper_timetag_difference(clock->now,
+                                                                   sync->response.timetag));
+            else
+                lo_message_add_double(m, 0.);
+            // need to send immediately
+            lo_bundle_add_message(b, admin_msg_strings[ADM_LINK_PING], m);
+#ifdef FORCE_ADMIN_TO_BUS
+            lo_send_bundle_from(admin->bus_addr, admin->mesh_server, b);
+#else
+            lo_send_bundle_from(receiver->admin_addr, admin->mesh_server, b);
+#endif
+            mapper_timetag_cpy(&sync->sent.timetag, lo_bundle_get_timestamp(b));
+            lo_bundle_free_messages(b);
+        }
+        receiver = receiver->next;
+    }
+}
+
 /*! This is the main function to be called once in a while from a
  *  program so that the admin bus can be automatically managed.
  */
@@ -587,13 +911,16 @@ int mapper_admin_poll(mapper_admin admin)
     if (md)
         md->flags &= ~FLAGS_SENT_ALL_DEVICE_MESSAGES;
 
-    while (count < 10 && lo_server_recv_noblock(admin->admin_server, 0)) {
+    while (count < 10 && (lo_server_recv_noblock(admin->bus_server, 0)
+           + lo_server_recv_noblock(admin->mesh_server, 0))) {
         count++;
     }
     admin->msgs_recvd += count;
 
-    if (!md)
+    if (!md) {
+        mapper_admin_maybe_send_ping(admin, 0);
         return count;
+    }
 
     /* If the ordinal is not yet locked, process collision timing.
      * Once the ordinal is locked it won't change. */
@@ -611,10 +938,11 @@ int mapper_admin_poll(mapper_admin admin)
             mdev_registered(md);
 
             /* Send registered msg. */
-            lo_send(admin->admin_addr, "/name/registered",
+            lo_send(admin->bus_addr, "/name/registered",
                     "s", mdev_name(md));
 
             mapper_admin_add_device_methods(admin, md);
+            mapper_admin_maybe_send_ping(admin, 1);
 
             trace("</%s.?::%p> registered as <%s>\n",
                   md->props.identifier, admin, mdev_name(md));
@@ -622,36 +950,8 @@ int mapper_admin_poll(mapper_admin admin)
         }
     }
     else {
-        if (md->flags & FLAGS_DEVICE_ATTRIBS_CHANGED) {
-            md->flags &= ~FLAGS_DEVICE_ATTRIBS_CHANGED;
-            mapper_admin_send_device(admin, md);
-        }
         // Send out clock sync messages occasionally
-        mapper_clock_t *clock = &admin->clock;
-        mapper_clock_now(clock, &clock->now);
-        if (clock->now.sec >= clock->next_ping) {
-            lo_bundle b = lo_bundle_new(clock->now);
-            lo_message m = lo_message_new();
-            if (m) {
-                lo_message_add_int32(m, mdev_id(admin->device));
-                lo_message_add_int32(m, clock->message_id);
-                lo_message_add_float(m, clock->confidence);
-                lo_message_add_int32(m, clock->remote.device_id);
-                lo_message_add_int32(m, clock->remote.message_id);
-                lo_message_add_double(m, clock->remote.device_id ?
-                                      mapper_timetag_difference(clock->now,
-                                                                clock->remote.timetag) : 0);
-                lo_bundle_add_message(b, "/sync", m);
-                lo_send_bundle(admin->admin_addr, b);
-                clock->local[clock->local_index].message_id = clock->message_id;
-                clock->local[clock->local_index].timetag.sec = clock->now.sec;
-                clock->local[clock->local_index].timetag.frac = clock->now.frac;
-                clock->local_index = (clock->local_index + 1) % 10;
-                clock->message_id = (clock->message_id + 1) % 10;
-                clock->next_ping = clock->now.sec + 5 + (rand() % 5);
-            }
-            lo_bundle_free_messages(b);
-        }
+        mapper_admin_maybe_send_ping(admin, 0);
     }
     return count;
 }
@@ -697,10 +997,10 @@ static int check_collisions(mapper_admin admin,
     return 0;
 }
 
-void _real_mapper_admin_send(mapper_admin admin,
-                             int msg_index,
-                             const char *path,
-                             const char *types, ...)
+void _real_mapper_admin_bundle_message(mapper_admin admin,
+                                       int msg_index,
+                                       const char *path,
+                                       const char *types, ...)
 {
     char t[]=" ";
 
@@ -718,7 +1018,8 @@ void _real_mapper_admin_send(mapper_admin admin,
         switch (t[0]) {
         case 'i': lo_message_add(m, t, va_arg(aq, int)); break;
         case 's': lo_message_add(m, t, va_arg(aq, char*)); break;
-        case 'f': lo_message_add(m, t, va_arg(aq, double)); break;
+        case 'f':
+        case 'd': lo_message_add(m, t, va_arg(aq, double)); break;
         default:
             die_unless(0, "message %s, unknown type '%c'\n",
                        path, t[0]);
@@ -730,10 +1031,8 @@ void _real_mapper_admin_send(mapper_admin admin,
 
     va_end(aq);
 
-    if (!mapper_admin_check_bundle(admin)) {
-        lo_bundle_add_message(admin->bundle, msg_index == -1 ?
-                              path : admin_msg_strings[msg_index], m);
-    }
+    lo_bundle_add_message(admin->bundle, msg_index == -1 ?
+                          path : admin_msg_strings[msg_index], m);
 
     /* Since liblo doesn't cache path strings, we must send the bundle
      * immediately if the path is non-standard. */
@@ -741,12 +1040,12 @@ void _real_mapper_admin_send(mapper_admin admin,
         mapper_admin_send_bundle(admin);
 }
 
-void _real_mapper_admin_send_with_params(mapper_admin admin,
-                                         mapper_message_t *params,
-                                         mapper_string_table_t *extra,
-                                         int msg_index,
-                                         const char *path,
-                                         const char *types, ...)
+void _real_mapper_admin_bundle_message_with_params(mapper_admin admin,
+                                                   mapper_message_t *params,
+                                                   mapper_string_table_t *extra,
+                                                   int msg_index,
+                                                   const char *path,
+                                                   const char *types, ...)
 {
     char t[]=" ";
 
@@ -780,10 +1079,8 @@ void _real_mapper_admin_send_with_params(mapper_admin admin,
     if (extra)
         mapper_msg_add_value_table(m, extra);
 
-    if (!mapper_admin_check_bundle(admin)) {
-        lo_bundle_add_message(admin->bundle, msg_index == -1 ?
-                              path : admin_msg_strings[msg_index], m);
-    }
+    lo_bundle_add_message(admin->bundle, msg_index == -1 ?
+                          path : admin_msg_strings[msg_index], m);
 
     /* Since liblo doesn't cache path strings, we must send the bundle
      * immediately if the path is non-standard. */
@@ -799,7 +1096,7 @@ static void mapper_admin_send_device(mapper_admin admin,
     if (device->flags & FLAGS_SENT_DEVICE_INFO)
         return;
 
-    mapper_admin_send(
+    mapper_admin_bundle_message(
         admin, ADM_DEVICE, 0, "s", mdev_name(device),
         AT_LIB_VERSION, PACKAGE_VERSION,
         AT_PORT, device->props.port,
@@ -815,6 +1112,64 @@ static void mapper_admin_send_device(mapper_admin admin,
     device->flags |= FLAGS_SENT_DEVICE_INFO;
 }
 
+void mapper_admin_send_signal(mapper_admin admin, mapper_device md,
+                              mapper_signal sig)
+{
+    char sig_name[1024];
+    msig_full_name(sig, sig_name, 1024);
+    mapper_admin_bundle_message(
+        admin, ADM_SIGNAL, 0, "s", sig_name,
+        AT_DIRECTION, sig->props.is_output ? "output" : "input",
+        AT_TYPE, sig->props.type,
+        AT_LENGTH, sig->props.length,
+        sig->props.unit ? AT_UNITS : -1, sig,
+        sig->props.minimum ? AT_MIN : -1, sig,
+        sig->props.maximum ? AT_MAX : -1, sig,
+        sig->props.num_instances > 1 ? AT_INSTANCES : -1, sig,
+        sig->props.rate ? AT_RATE : -1, sig,
+        AT_EXTRA, sig->props.extra);
+}
+
+void mapper_admin_send_signal_removed(mapper_admin admin, mapper_device md,
+                                      mapper_signal sig)
+{
+    char sig_name[1024];
+    msig_full_name(sig, sig_name, 1024);
+    mapper_admin_bundle_message(admin, sig->props.is_output ?
+                                ADM_OUTPUT_REMOVED : ADM_INPUT_REMOVED,
+                                0, "s", sig_name);
+}
+
+static void mapper_admin_send_inputs(mapper_admin admin, mapper_device md,
+                                     int min, int max)
+{
+    if (min < 0)
+        min = 0;
+    else if (min > md->props.num_inputs)
+        return;
+    if (max < 0 || max > md->props.num_inputs)
+        max = md->props.num_inputs-1;
+
+    int i = min;
+    for (; i <= max; i++)
+        mapper_admin_send_signal(admin, md, md->inputs[i]);
+}
+
+static void mapper_admin_send_outputs(mapper_admin admin, mapper_device md,
+                                      int min, int max)
+{
+    if (min < 0)
+        min = 0;
+    else if (min > md->props.num_outputs)
+        return;
+    if (max < 0 || max > md->props.num_outputs)
+        max = md->props.num_outputs-1;
+
+    int i = min;
+    for (; i <= max; i++)
+        mapper_admin_send_signal(admin, md, md->outputs[i]);
+}
+
 static void mapper_admin_send_linked(mapper_admin admin,
                                      mapper_link link,
                                      int is_outgoing)
@@ -845,8 +1200,24 @@ static void mapper_admin_send_linked(mapper_admin admin,
 
     mapper_link_prepare_osc_message(m, link);
 
-    if (!mapper_admin_check_bundle(admin)) {
-        lo_bundle_add_message(admin->bundle, "/linked", m);
+    lo_bundle_add_message(admin->bundle, "/linked", m);
+}
+
+static void mapper_admin_send_links_in(mapper_admin admin, mapper_device md)
+{
+    mapper_receiver r = md->receivers;
+    while (r) {
+        mapper_admin_send_linked(admin, r, 0);
+        r = r->next;
+    }
+}
+
+static void mapper_admin_send_links_out(mapper_admin admin, mapper_device md)
+{
+    mapper_router r = md->routers;
+    while (r) {
+        mapper_admin_send_linked(admin, r, 1);
+        r = r->next;
     }
 }
 
@@ -883,8 +1254,58 @@ static void mapper_admin_send_connected(mapper_admin admin,
 
     mapper_connection_prepare_osc_message(m, c);
 
-    if (!mapper_admin_check_bundle(admin)) {
-        lo_bundle_add_message(admin->bundle, "/connected", m);
+    lo_bundle_add_message(admin->bundle, "/connected", m);
+}
+
+static void mapper_admin_send_connections_in(mapper_admin admin,
+                                             mapper_device md,
+                                             int min, int max)
+{
+    // TODO: allow requesting connections by parent link?
+    int i = 0;
+    mapper_receiver rc = md->receivers;
+    while (rc) {
+        mapper_receiver_signal rs = rc->signals;
+        while (rs) {
+			mapper_connection c = rs->connections;
+            while (c) {
+                if (max > 0 && i > max)
+                    break;
+                if (i >= min) {
+                    mapper_admin_send_connected(admin, rc, c, i, 0);
+                }
+                c = c->next;
+                i++;
+            }
+            rs = rs->next;
+        }
+        rc = rc->next;
+    }
+}
+
+static void mapper_admin_send_connections_out(mapper_admin admin,
+                                              mapper_device md,
+                                              int min, int max)
+{
+    // TODO: allow requesting connections by parent link?
+    int i = 0;
+    mapper_router rt = md->routers;
+    while (rt) {
+        mapper_router_signal rs = rt->signals;
+        while (rs) {
+			mapper_connection c = rs->connections;
+            while (c) {
+                if (max > 0 && i > max)
+                    break;
+                if (i >= min) {
+                    mapper_admin_send_connected(admin, rt, c, i, 1);
+                }
+                c = c->next;
+                i++;
+            }
+            rs = rs->next;
+        }
+        rt = rt->next;
     }
 }
 
@@ -892,18 +1313,18 @@ static void mapper_admin_send_connected(mapper_admin admin,
 /* Internal OSC message handlers. */
 /**********************************/
 
-/*! Respond to /who by announcing the current device information. */
+/*! Respond to /who by announcing the basic device information. */
 static int handler_who(const char *path, const char *types, lo_arg **argv,
                        int argc, lo_message msg, void *user_data)
 {
     mapper_admin admin = (mapper_admin) user_data;
+    mapper_admin_maybe_send_ping(admin, 1);
 
-    mapper_admin_send_device(admin, admin->device);
+    trace("%s received /who\n", mdev_name(admin->device));
 
     return 0;
 }
 
-
 /*! Register information about port and host for the device. */
 static int handler_device(const char *path, const char *types,
                           lo_arg **argv, int argc, lo_message msg,
@@ -921,6 +1342,8 @@ static int handler_device(const char *path, const char *types,
 
     const char *name = &argv[0]->s;
 
+    trace("<monitor> got /device %s + %i arguments\n", name, argc-1);
+
     mapper_message_t params;
     mapper_msg_parse_params(&params, path, &types[1],
                             argc-1, &argv[1]);
@@ -936,9 +1359,12 @@ static int handler_device(const char *path, const char *types,
                 params.lengths[AT_IP] = 1;
             }
         }
+        else
+            trace("Couldn't retrieve host for device %s.\n", name);
     }
 
-    mapper_db_add_or_update_device_params(db, name, &params);
+    mapper_clock_now(&admin->clock, &admin->clock.now);
+    mapper_db_add_or_update_device_params(db, name, &params, &admin->clock.now);
 
     return 0;
 }
@@ -963,21 +1389,63 @@ static int handler_logout(const char *path, const char *types,
 
     char *name = &argv[0]->s;
 
-    trace("got /logout %s\n", name);
+    trace("<%s> got /logout %s\n",
+          (md && md->ordinal.locked) ? mdev_name(md) : "monitor", name);
 
     if (mon) {
         mapper_db_remove_device_by_name(db, name);
+
+        // remove subscriptions
+        mapper_monitor_unsubscribe(mon, name);
     }
 
     // If device exists and is registered
     if (md && md->ordinal.locked) {
+        // Check if we have any links to this device, if so remove them
+        mapper_router router =
+            mapper_router_find_by_dest_name(md->routers, name);
+        if (router) {
+            // Call the local link handler if it exists
+            if (md->link_cb)
+                md->link_cb(md, &router->props, MDEV_LOCAL_DESTROYED,
+                            md->link_cb_userdata);
+
+            trace("<%s> Removing link to expired device %s.\n",
+                  mdev_name(md), router->props.dest_name);
+
+            mdev_remove_router(md, router);
+
+            // Inform subscribers
+            mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_OUT);
+            mapper_admin_bundle_message(admin, ADM_UNLINKED, 0, "ss",
+                                        mdev_name(md), name);
+        }
+
+        mapper_receiver receiver =
+            mapper_receiver_find_by_src_name(md->receivers, name);
+        if (receiver) {
+            // Call the local link handler if it exists
+            if (md->link_cb)
+                md->link_cb(md, &receiver->props, MDEV_LOCAL_DESTROYED,
+                            md->link_cb_userdata);
+
+            trace("<%s> Removing link from expired device %s.\n",
+                  mdev_name(md), receiver->props.dest_name);
+
+            mdev_remove_receiver(md, receiver);
+
+            // Inform subscribers
+            mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_IN);
+            mapper_admin_bundle_message(admin, ADM_UNLINKED, 0, "ss",
+                                        name, mdev_name(md));
+        }
+
         /* Parse the ordinal from the complete name which is in the
          * format: /<name>.<n> */
         s = name;
         if (*s++ != '/')
             return 0;
-        while (*s != '.' && *s++) {
-        }
+        while (*s != '.' && *s++) {}
         ordinal = atoi(++s);
 
         // If device name matches
@@ -995,145 +1463,166 @@ static int handler_logout(const char *path, const char *types,
     return 0;
 }
 
-/*! Respond to /signals/input/get by enumerating all supported
- *  inputs. */
-static int handler_id_n_signals_input_get(const char *path,
-                                          const char *types,
-                                          lo_arg **argv, int argc,
-                                          lo_message msg,
-                                          void *user_data)
+// Add/renew/remove a monitor subscription.
+static void mapper_admin_manage_subscriber(mapper_admin admin, lo_address address,
+                                           int flags, int timeout_seconds,
+                                           int revision)
 {
-    mapper_admin admin = (mapper_admin) user_data;
-    mapper_device md = admin->device;
-    char sig_name[1024];
-    int i = 0, j = md->props.n_inputs - 1;
+    mapper_admin_subscriber *s = &admin->subscribers;
+    const char *ip = lo_address_get_hostname(address);
+    const char *port = lo_address_get_port(address);
+    if (!ip || !port)
+        return;
 
-    if (!md->props.n_inputs)
-        return 0;
+    mapper_clock_t *clock = &admin->clock;
+    mapper_clock_now(clock, &clock->now);
+
+    while (*s) {
+        if (strcmp(ip, lo_address_get_hostname((*s)->address))==0 &&
+            strcmp(port, lo_address_get_port((*s)->address))==0) {
+            // subscriber already exists
+            if (!flags || !timeout_seconds) {
+                // remove subscription
+                mapper_admin_subscriber temp = *s;
+                int prev_flags = temp->flags;
+                *s = temp->next;
+                if (temp->address)
+                    lo_address_free(temp->address);
+                free(temp);
+                if (!flags || !(flags &= ~prev_flags))
+                    return;
+            }
+            else {
+                // reset timeout
+                (*s)->lease_expiration_sec = clock->now.sec + timeout_seconds;
+                if ((*s)->flags == flags) {
+                    if (revision)
+                        return;
+                    else
+                        break;
+                }
+                int temp = flags;
+                flags &= ~(*s)->flags;
+                (*s)->flags = temp;
+            }
+            break;
+        }
+        s = &(*s)->next;
+    }
 
-    if (!argc && (md->flags & FLAGS_SENT_DEVICE_INPUTS))
-        return 0;
+    if (!flags)
+        return;
 
-    if (argc > 0) {
-        if (types[0] == 'i')
-            i = argv[0]->i;
-        else if (types[0] == 'f')
-            i = (int)argv[0]->f;
-        if (i < 0)
-            i = 0;
-        else if (i >= md->props.n_inputs)
-            i = md->props.n_inputs - 1;
-        j = i;
-    }
-    if (argc > 1) {
-        if (types[1] == 'i')
-            j = argv[1]->i;
-        else if (types[1] == 'f')
-            j = (int)argv[1]->f;
-        if (j < i)
-            j = i;
-        if (j >= md->props.n_inputs)
-            j = md->props.n_inputs - 1;
-    }
-
-    for (; i <= j; i++) {
-        mapper_signal sig = md->inputs[i];
-        msig_full_name(sig, sig_name, 1024);
-        mapper_admin_send(
-            admin, ADM_SIGNAL, 0, "s", sig_name,
-            AT_ID, i,
-            AT_DIRECTION, "input",
-            AT_TYPE, sig->props.type,
-            AT_LENGTH, sig->props.length,
-            sig->props.unit ? AT_UNITS : -1, sig,
-            sig->props.minimum ? AT_MIN : -1, sig,
-            sig->props.maximum ? AT_MAX : -1, sig,
-            sig->props.num_instances > 1 ? AT_INSTANCES : -1, sig,
-            sig->props.rate ? AT_RATE : -1, sig,
-            AT_EXTRA, sig->props.extra);
-    }
-
-    md->flags |= FLAGS_SENT_DEVICE_INPUTS;
+    if (!(*s)) {
+        // add new subscriber
+        mapper_admin_subscriber sub = malloc(sizeof(struct _mapper_admin_subscriber));
+        sub->address = lo_address_new(ip, port);
+        sub->lease_expiration_sec = clock->now.sec + timeout_seconds;
+        sub->flags = flags;
+        sub->next = admin->subscribers;
+        admin->subscribers = sub;
+        s = ⊂
+    }
 
-    return 0;
+    if (revision == admin->device->props.version)
+        return;
+
+    // bring new subscriber up to date
+    mapper_admin_set_bundle_dest_mesh(admin, (*s)->address);
+    if (flags & SUB_DEVICE)
+        mapper_admin_send_device(admin, admin->device);
+    if (flags & SUB_DEVICE_INPUTS)
+        mapper_admin_send_inputs(admin, admin->device, -1, -1);
+    if (flags & SUB_DEVICE_OUTPUTS)
+        mapper_admin_send_outputs(admin, admin->device, -1, -1);
+    if (flags & SUB_DEVICE_LINKS_IN)
+        mapper_admin_send_links_in(admin, admin->device);
+    if (flags & SUB_DEVICE_LINKS_OUT)
+        mapper_admin_send_links_out(admin, admin->device);
+    if (flags & SUB_DEVICE_CONNECTIONS_IN)
+        mapper_admin_send_connections_in(admin, admin->device, -1, -1);
+    if (flags & SUB_DEVICE_CONNECTIONS_OUT)
+        mapper_admin_send_connections_out(admin, admin->device, -1, -1);
 }
 
-/*! Respond to /signals/output/get by enumerating all supported
- *  outputs. */
-static int handler_id_n_signals_output_get(const char *path,
-                                           const char *types,
-                                           lo_arg **argv, int argc,
-                                           lo_message msg,
-                                           void *user_data)
+/*! Respond to /subscribe message by adding or renewing a subscription. */
+static int handler_device_subscribe(const char *path, const char *types,
+                                    lo_arg **argv, int argc, lo_message msg,
+                                    void *user_data)
 {
     mapper_admin admin = (mapper_admin) user_data;
     mapper_device md = admin->device;
-    char sig_name[1024];
-    int i = 0, j = md->props.n_outputs - 1;
-
-    if (!md->props.n_outputs)
-        return 0;
-
-    if (!argc && (md->flags & FLAGS_SENT_DEVICE_OUTPUTS))
-        return 0;
+    int version = -1;
+
+    lo_address a  = lo_message_get_source(msg);
+    if (!a || !argc) return 0;
+
+    int i, flags = 0, timeout_seconds = 0;
+    for (i = 0; i < argc; i++) {
+        if (types[i] != 's' && types[i] != 'S')
+            break;
+        else if (strcmp(&argv[i]->s, "all")==0)
+            flags = SUB_DEVICE_ALL;
+        else if (strcmp(&argv[i]->s, "device")==0)
+            flags |= SUB_DEVICE;
+        else if (strcmp(&argv[i]->s, "inputs")==0)
+            flags |= SUB_DEVICE_INPUTS;
+        else if (strcmp(&argv[i]->s, "outputs")==0)
+            flags |= SUB_DEVICE_OUTPUTS;
+        else if (strcmp(&argv[i]->s, "links")==0)
+            flags |= SUB_DEVICE_LINKS;
+        else if (strcmp(&argv[i]->s, "links_in")==0)
+            flags |= SUB_DEVICE_LINKS_IN;
+        else if (strcmp(&argv[i]->s, "links_out")==0)
+            flags |= SUB_DEVICE_LINKS_OUT;
+        else if (strcmp(&argv[i]->s, "connections")==0)
+            flags |= SUB_DEVICE_CONNECTIONS;
+        else if (strcmp(&argv[i]->s, "connections_in")==0)
+            flags |= SUB_DEVICE_CONNECTIONS_IN;
+        else if (strcmp(&argv[i]->s, "connections_out")==0)
+            flags |= SUB_DEVICE_CONNECTIONS_OUT;
+        else if (strcmp(&argv[i]->s, "@version")==0) {
+            // next argument is last device version recorded by subscriber
+            ++i;
+            if (i < argc && types[i] == 'i')
+                version = argv[i]->i;
+        }
+        else if (strcmp(&argv[i]->s, "@lease")==0) {
+            // next argument is lease timeout in seconds
+            ++i;
+            if (types[i] == 'i')
+                timeout_seconds = argv[i]->i;
+            else if (types[i] == 'f')
+                timeout_seconds = (int)argv[i]->f;
+            else if (types[i] == 'd')
+                timeout_seconds = (int)argv[i]->d;
+            else {
+                trace("<%s> error parsing @lease property in /subscribe.\n",
+                      mdev_name(md));
+            }
+        }
+    }
 
-    if (argc > 0) {
-        if (types[0] == 'i')
-            i = argv[0]->i;
-        else if (types[0] == 'f')
-            i = (int)argv[0]->f;
-        if (i < 0)
-            i = 0;
-        else if (i >= md->props.n_outputs)
-            i = md->props.n_outputs - 1;
-        j = i;
-    }
-    if (argc > 1) {
-        if (types[1] == 'i')
-            j = argv[1]->i;
-        else if (types[1] == 'f')
-            j = (int)argv[1]->f;
-        if (j < i)
-            j = i;
-        if (j >= md->props.n_outputs)
-            j = md->props.n_outputs - 1;
-    }
-
-    for (; i <= j; i++) {
-        mapper_signal sig = md->outputs[i];
-        msig_full_name(sig, sig_name, 1024);
-        mapper_admin_send(
-            admin, ADM_SIGNAL, 0, "s", sig_name,
-            AT_ID, i,
-            AT_DIRECTION, "output",
-            AT_TYPE, sig->props.type,
-            AT_LENGTH, sig->props.length,
-            sig->props.unit ? AT_UNITS : -1, sig,
-            sig->props.minimum ? AT_MIN : -1, sig,
-            sig->props.maximum ? AT_MAX : -1, sig,
-            sig->props.num_instances > 1 ? AT_INSTANCES : -1, sig,
-            sig->props.rate ? AT_RATE : -1, sig,
-            AT_EXTRA, sig->props.extra);
-    }
-
-    md->flags |= FLAGS_SENT_DEVICE_OUTPUTS;
+    // add or renew subscription
+    mapper_admin_manage_subscriber(admin, a, flags, timeout_seconds, version);
 
     return 0;
 }
 
-/*! Respond to /signals/get by enumerating all supported inputs and
- *  outputs. */
-static int handler_id_n_signals_get(const char *path, const char *types,
-                                    lo_arg **argv, int argc,
-                                    lo_message msg, void *user_data)
+/*! Respond to /unsubscribe message by removing a subscription. */
+static int handler_device_unsubscribe(const char *path, const char *types,
+                                      lo_arg **argv, int argc, lo_message msg,
+                                      void *user_data)
 {
-    handler_id_n_signals_input_get(path, types, argv, argc, msg,
-                                   user_data);
-    handler_id_n_signals_output_get(path, types, argv, argc, msg,
-                                    user_data);
+    mapper_admin admin = (mapper_admin) user_data;
 
-    return 0;
+    lo_address a  = lo_message_get_source(msg);
+    if (!a) return 0;
 
+    // remove subscription
+    mapper_admin_manage_subscriber(admin, a, 0, 0, 0);
+
+    return 0;
 }
 
 /*! Register information about a signal. */
@@ -1175,6 +1664,72 @@ static int handler_signal_info(const char *path, const char *types,
 	return 0;
 }
 
+/*! Unregister information about a removed input signal. */
+static int handler_input_signal_removed(const char *path, const char *types,
+                                        lo_arg **argv, int argc, lo_message m,
+                                        void *user_data)
+{
+    mapper_admin admin = (mapper_admin) user_data;
+    mapper_monitor mon = admin->monitor;
+    mapper_db db = mapper_monitor_get_db(mon);
+
+    if (argc < 1)
+        return 1;
+
+    if (types[0] != 's' && types[0] != 'S')
+        return 1;
+
+    const char *full_sig_name = &argv[0]->s;
+    const char *sig_name = strchr(full_sig_name+1, '/');
+    if (!sig_name)
+        return 1;
+
+    int dev_name_len = sig_name-full_sig_name;
+    if (dev_name_len >= 1024)
+        return 0;
+
+    char dev_name[1024];
+    strncpy(dev_name, full_sig_name, dev_name_len);
+    dev_name[dev_name_len]=0;
+
+    mapper_db_remove_input_by_name(db, dev_name, sig_name);
+
+	return 0;
+}
+
+/*! Unregister information about a removed input signal. */
+static int handler_output_signal_removed(const char *path, const char *types,
+                                         lo_arg **argv, int argc, lo_message m,
+                                         void *user_data)
+{
+    mapper_admin admin = (mapper_admin) user_data;
+    mapper_monitor mon = admin->monitor;
+    mapper_db db = mapper_monitor_get_db(mon);
+
+    if (argc < 1)
+        return 1;
+
+    if (types[0] != 's' && types[0] != 'S')
+        return 1;
+
+    const char *full_sig_name = &argv[0]->s;
+    const char *sig_name = strchr(full_sig_name+1, '/');
+    if (!sig_name)
+        return 1;
+
+    int dev_name_len = sig_name-full_sig_name;
+    if (dev_name_len >= 1024)
+        return 0;
+
+    char dev_name[1024];
+    strncpy(dev_name, full_sig_name, dev_name_len);
+    dev_name[dev_name_len]=0;
+
+    mapper_db_remove_output_by_name(db, dev_name, sig_name);
+
+	return 0;
+}
+
 /*! Repond to name collisions during allocation, help suggest IDs once allocated. */
 static int handler_device_name_registered(const char *path, const char *types,
                                           lo_arg **argv, int argc,
@@ -1282,7 +1837,7 @@ static int handler_device_name_probe(const char *path, const char *types,
             }
             /* Name may not yet be registered, so we can't use
              * mapper_admin_send() here. */
-            lo_send(admin->admin_addr, "/name/registered",
+            lo_send(admin->bus_addr, "/name/registered",
                     "sii", name, temp_id,
                     (md->ordinal.value+i+1));
         }
@@ -1334,7 +1889,10 @@ static int handler_device_link(const char *path, const char *types,
     params.values[AT_DEST_PORT] = &arg_port;
     params.types[AT_DEST_PORT] = "i";
     params.lengths[AT_DEST_PORT] = 1;
-    mapper_admin_send_with_params(
+
+    // TODO: check if src ip and port are available as metadata, send directly
+    mapper_admin_set_bundle_dest_bus(admin);
+    mapper_admin_bundle_message_with_params(
         admin, &params, 0, ADM_LINK_TO, 0, "ss", src_name, dest_name);
 
     return 0;
@@ -1348,9 +1906,10 @@ static int handler_device_linkTo(const char *path, const char *types,
     mapper_admin admin = (mapper_admin) user_data;
     mapper_device md = admin->device;
 
-    const char *src_name, *dest_name, *host=0;
-    int port;
+    const char *src_name, *dest_name, *host=0, *admin_port;
+    int data_port;
     mapper_message_t params;
+    lo_address a = NULL;
 
     if (argc < 2)
         return 0;
@@ -1391,26 +1950,23 @@ static int handler_device_linkTo(const char *path, const char *types,
         return 0;
     }
 
-    // Retrieve the IP if specified.
-    host = mapper_msg_get_param_if_string(&params, AT_IP);
+    // Find the sender's hostname
+    a = lo_message_get_source(msg);
+    host = lo_address_get_hostname(a);
+    admin_port = lo_address_get_port(a);
     if (!host) {
-        // Find the sender's hostname
-        lo_address a = lo_message_get_source(msg);
-        host = lo_address_get_hostname(a);
-        if (!host) {
-            trace("can't perform /linkTo, host unknown\n");
-            return 0;
-        }
+        trace("can't perform /linkTo, host unknown\n");
+        return 0;
     }
 
     // Retrieve the port
-    if (mapper_msg_get_param_if_int(&params, AT_DEST_PORT, &port)) {
+    if (mapper_msg_get_param_if_int(&params, AT_DEST_PORT, &data_port)) {
         trace("can't perform /linkTo, port unknown\n");
         return 0;
     }
 
     // Creation of a new router added to the source.
-    router = mapper_router_new(md, host, port, dest_name);
+    router = mapper_router_new(md, host, atoi(admin_port), data_port, dest_name);
     if (!router) {
         trace("can't perform /linkTo, NULL router\n");
         return 0;
@@ -1425,11 +1981,18 @@ static int handler_device_linkTo(const char *path, const char *types,
         md->link_cb(md, &router->props, MDEV_LOCAL_ESTABLISHED,
                     md->link_cb_userdata);
 
-    // Announce the result.
+    // Announce the result to destination and subscribers.
+    if (!a)
+        mapper_admin_set_bundle_dest_bus(admin);
+    else
+        mapper_admin_set_bundle_dest_mesh(admin, a);
+
+    mapper_admin_send_linked(admin, router, 1);
+    mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_OUT);
     mapper_admin_send_linked(admin, router, 1);
 
     trace("<%s> added new router to %s -> host: %s, port: %d\n",
-          mdev_name(md), dest_name, host, port);
+          mdev_name(md), dest_name, host, data_port);
 
     return 0;
 }
@@ -1444,8 +2007,8 @@ static int handler_device_linked(const char *path, const char *types,
     mapper_monitor mon = admin->monitor;
     mapper_db db = mapper_monitor_get_db(mon);
 
-    const char *src_name, *dest_name, *host=0;
-    int port = -1;
+    const char *src_name, *dest_name, *host=0, *admin_port;
+    int data_port = -1;
 
     if (argc < 2)
         return 0;
@@ -1465,7 +2028,7 @@ static int handler_device_linked(const char *path, const char *types,
         return 0;
     if (mon)
         mapper_db_add_or_update_link_params(db, src_name, dest_name, &params);
-    if (!md || strcmp(mdev_name(md), dest_name))
+    if (!md || !mdev_name(md) || strcmp(mdev_name(md), dest_name))
         return 0;
 
     // Add a receiver data structure
@@ -1477,6 +2040,10 @@ static int handler_device_linked(const char *path, const char *types,
         if (argc <= 2)
             return 0;
         if (mapper_receiver_set_from_message(receiver, &params)) {
+            // Inform subscribers
+            mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_IN);
+            mapper_admin_send_linked(admin, receiver, 0);
+
             // Call local link handler if it exists
             if (md->link_cb)
                 md->link_cb(md, &receiver->props, MDEV_LOCAL_MODIFIED,
@@ -1488,15 +2055,17 @@ static int handler_device_linked(const char *path, const char *types,
     // Find the sender's hostname
     lo_address a = lo_message_get_source(msg);
     host = lo_address_get_hostname(a);
-    if (!host) {
+    admin_port = lo_address_get_port(a);
+    if (!host || !admin_port) {
         trace("can't add receiver on /linked, host unknown\n");
         return 0;
     }
 
     // Retrieve the src device port if it is defined
-    mapper_msg_get_param_if_int(&params, AT_SRC_PORT, &port);
+    mapper_msg_get_param_if_int(&params, AT_SRC_PORT, &data_port);
 
-    receiver = mapper_receiver_new(md, host, port, src_name);
+    receiver = mapper_receiver_new(md, host, atoi(admin_port),
+                                   data_port, src_name);
     if (!receiver) {
         trace("Error: NULL receiver\n");
         return 0;
@@ -1505,6 +2074,10 @@ static int handler_device_linked(const char *path, const char *types,
     if (argc > 2)
         mapper_receiver_set_from_message(receiver, &params);
 
+    // Inform subscribers
+    mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_IN);
+    mapper_admin_send_linked(admin, receiver, 0);
+
     // Call local link handler if it exists
     if (md->link_cb)
     md->link_cb(md, &receiver->props, MDEV_LOCAL_ESTABLISHED,
@@ -1564,82 +2137,21 @@ static int handler_device_link_modify(const char *path, const char *types,
         // increment device version
         md->version += updated;
 
+        // Inform subscribers
+        mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_OUT);
+        mapper_admin_send_linked(admin, router, 0);
+
         // Call local link handler if it exists
         if (md->link_cb)
             md->link_cb(md, &router->props, MDEV_LOCAL_MODIFIED,
                         md->link_cb_userdata);
 
-        // Announce the result.
-        mapper_admin_send_linked(admin, router, 1);
-
         trace("<%s> modified link to %s\n", mdev_name(md), dest_name);
     }
 
     return 0;
 }
 
-/*! Report existing links to the network */
-static int handler_device_links_get(const char *path, const char *types,
-                                    lo_arg **argv, int argc,
-                                    lo_message msg, void *user_data)
-{
-    handler_device_links_in_get(path, types, argv, argc, msg, user_data);
-    handler_device_links_out_get(path, types, argv, argc, msg, user_data);
-
-    return 0;
-}
-
-/*! Report existing incoming links to the network */
-static int handler_device_links_in_get(const char *path, const char *types,
-                                       lo_arg **argv, int argc,
-                                       lo_message msg, void *user_data)
-{
-    mapper_admin admin = (mapper_admin) user_data;
-    mapper_device md = admin->device;
-    mapper_router receiver = md->receivers;
-
-    trace("<%s> got %s/links/get\n", mdev_name(md), path);
-
-    if (md->flags & FLAGS_SENT_DEVICE_LINKS_IN)
-        return 0;
-
-    /* Iterate through outgoing links */
-    while (receiver != NULL) {
-        mapper_admin_send_linked(admin, receiver, 0);
-        receiver = receiver->next;
-    }
-
-    md->flags |= FLAGS_SENT_DEVICE_LINKS_IN;
-
-    return 0;
-}
-
-/*! Report existing outgoing links to the network */
-static int handler_device_links_out_get(const char *path, const char *types,
-                                       lo_arg **argv, int argc,
-                                       lo_message msg, void *user_data)
-{
-    mapper_admin admin = (mapper_admin) user_data;
-    mapper_device md = admin->device;
-    mapper_router router = md->routers;
-
-    trace("<%s> got %s/links/get\n", mdev_name(md),
-          mdev_name(md));
-
-    if (md->flags & FLAGS_SENT_DEVICE_LINKS_OUT)
-        return 0;
-
-    /* Iterate through outgoing links */
-    while (router != NULL) {
-        mapper_admin_send_linked(admin, router, 1);
-        router = router->next;
-    }
-
-    md->flags |= FLAGS_SENT_DEVICE_LINKS_OUT;
-
-    return 0;
-}
-
 /*! Unlink two devices. */
 static int handler_device_unlink(const char *path, const char *types,
                                  lo_arg **argv, int argc, lo_message msg,
@@ -1663,7 +2175,6 @@ static int handler_device_unlink(const char *path, const char *types,
           src_name, dest_name, argc-2);
 
     mapper_message_t params;
-    // add arguments from /unlink if any
     if (mapper_msg_parse_params(&params, path, &types[2],
                                 argc-2, &argv[2]))
     {
@@ -1684,9 +2195,17 @@ static int handler_device_unlink(const char *path, const char *types,
             md->link_cb(md, &router->props, MDEV_LOCAL_DESTROYED,
                         md->link_cb_userdata);
 
+        // Inform destination
+        mapper_admin_set_bundle_dest_mesh(admin, router->admin_addr);
+        mapper_admin_bundle_message_with_params(admin, &params, 0, ADM_UNLINKED,
+                                                0, "ss", mdev_name(md), dest_name);
+
+        // Inform subscribers
+        mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_OUT);
+        mapper_admin_bundle_message_with_params(admin, &params, 0, ADM_UNLINKED,
+                                                0, "ss", mdev_name(md), dest_name);
+
         mdev_remove_router(md, router);
-        mapper_admin_send_with_params(
-            admin, &params, 0, ADM_UNLINKED, 0, "ss", mdev_name(md), dest_name);
     }
     else {
         trace("<%s> no router for %s found in /unlink handler\n",
@@ -1738,7 +2257,7 @@ static int handler_device_unlinked(const char *path, const char *types,
             mapper_db_get_link_by_src_dest_names(db, src_name, dest_name));
     }
 
-    if (md) {
+    if (md && mdev_name(md)) {
         trace("<%s> got /unlinked %s %s + %i arguments\n",
               mdev_name(md), src_name, dest_name, argc-2);
 
@@ -1762,6 +2281,12 @@ static int handler_device_unlinked(const char *path, const char *types,
             if (md->link_cb)
                 md->link_cb(md, &receiver->props, MDEV_LOCAL_DESTROYED,
                             md->link_cb_userdata);
+
+            // Inform subscribers
+            mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_LINKS_IN);
+            mapper_admin_bundle_message(admin, ADM_UNLINKED, 0, "ss",
+                                        mdev_name(md), dest_name);
+
             mdev_remove_receiver(md, receiver);
         }
         else {
@@ -1848,7 +2373,6 @@ static int handler_signal_connect(const char *path, const char *types,
     }
 
     mapper_message_t params;
-
     // add arguments from /connect if any
     if (mapper_msg_parse_params(&params, path, &types[2],
                                 argc-2, &argv[2]))
@@ -1858,6 +2382,19 @@ static int handler_signal_connect(const char *path, const char *types,
         return 0;
     }
 
+    mapper_receiver receiver =
+        mapper_receiver_find_by_src_name(md->receivers, src_name);
+
+    /* If no link found, we simply stop here. The idea was floated
+     * that we could automatically create links, but it was agreed
+     * that this kind of logic would be best left up to client
+     * applications. */
+    if (!receiver) {
+        trace("<%s> not linked from '%s' on /connect.\n",
+              mdev_name(md), src_name);
+        return 0;
+    }
+
     // substitute some missing parameters with known properties
     lo_arg *arg_type = (lo_arg*) &input->props.type;
     params.values[AT_TYPE] = &arg_type;
@@ -1874,7 +2411,8 @@ static int handler_signal_connect(const char *path, const char *types,
     params.types[AT_INSTANCES] = "i";
     params.lengths[AT_INSTANCES] = 1;
 
-    mapper_admin_send_with_params(
+    mapper_admin_set_bundle_dest_mesh(admin, receiver->admin_addr);
+    mapper_admin_bundle_message_with_params(
         admin, &params, input->props.extra,
         ADM_CONNECT_TO, 0, "ss", src_name, dest_name,
         (!params.values[AT_MIN] && input->props.minimum) ? AT_MIN : -1, input,
@@ -2003,6 +2541,12 @@ static int handler_signal_connectTo(const char *path, const char *types,
         mapper_connection_set_from_message(c, &params);
     }
 
+    // Inform destination device
+    mapper_admin_set_bundle_dest_mesh(admin, router->admin_addr);
+    mapper_admin_send_connected(admin, router, c, -1, 1);
+
+    // Inform subscribers
+    mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_OUT);
     mapper_admin_send_connected(admin, router, c, -1, 1);
 
     // Call local connection handler if it exists
@@ -2052,8 +2596,8 @@ static int handler_signal_connected(const char *path, const char *types,
                                                   dest_name, &params);
     }
 
-    if (!md || prefix_cmp(dest_name, mdev_name(md),
-                          &dest_signal_name))
+    if (!md || !mdev_name(md) || prefix_cmp(dest_name, mdev_name(md),
+                                            &dest_signal_name))
         return 0;
 
     trace("<%s> got /connected %s %s + %d arguments\n",
@@ -2083,6 +2627,10 @@ static int handler_signal_connected(const char *path, const char *types,
         // connection already exists, add metadata
         int updated = mapper_connection_set_from_message(c, &params);
         if (updated) {
+            // Inform subscribers
+            mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_IN);
+            mapper_admin_send_connected(admin, receiver, c, -1, 0);
+
             // Call local connection handler if it exists
             if (md->connection_cb)
                 md->connection_cb(md, &receiver->props, input,
@@ -2091,8 +2639,7 @@ static int handler_signal_connected(const char *path, const char *types,
         }
         return 0;
     }
-
-    if (!c) {
+    else {
         /* Creation of a connection requires the type and length info. */
         if (!params.values[AT_SRC_TYPE] || !params.values[AT_SRC_LENGTH])
             return 0;
@@ -2114,18 +2661,20 @@ static int handler_signal_connected(const char *path, const char *types,
         // Add a flavourless connection
         c = mapper_receiver_add_connection(receiver, input, src_signal_name,
                                            src_type, src_length);
-    }
 
-    if (c && argc > 2) {
         /* Set its properties. */
         mapper_connection_set_from_message(c, &params);
-    }
 
-    // Call local connection handler if it exists
-    if (md->connection_cb)
-        md->connection_cb(md, &receiver->props, input,
-                          &c->props, MDEV_LOCAL_ESTABLISHED,
-                          md->connection_cb_userdata);
+        // Inform subscribers
+        mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_IN);
+        mapper_admin_send_connected(admin, receiver, c, -1, 0);
+
+        // Call local connection handler if it exists
+        if (md->connection_cb)
+            md->connection_cb(md, &receiver->props, input,
+                              &c->props, MDEV_LOCAL_ESTABLISHED,
+                              md->connection_cb_userdata);
+    }
 
     return 0;
 }
@@ -2196,7 +2745,14 @@ static int handler_signal_connection_modify(const char *path, const char *types,
 
     int updated = mapper_connection_set_from_message(c, &params);
     if (updated) {
+        // Inform destination device
+        mapper_admin_set_bundle_dest_mesh(admin, router->admin_addr);
+        mapper_admin_send_connected(admin, router, c, -1, 1);
+
+        // Inform subscribers
+        mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_OUT);
         mapper_admin_send_connected(admin, router, c, -1, 1);
+
         // Call local connection handler if it exists
         if (md->connection_cb)
             md->connection_cb(md, &router->props, output,
@@ -2274,8 +2830,13 @@ static int handler_signal_disconnect(const char *path, const char *types,
         return 0;
     }
 
-    mapper_admin_send(admin, ADM_DISCONNECTED, 0, "ss",
-                      src_name, dest_name);
+    // Inform destination and subscribers
+    mapper_admin_set_bundle_dest_mesh(admin, r->admin_addr);
+    mapper_admin_bundle_message(admin, ADM_DISCONNECTED, 0, "ss",
+                                src_name, dest_name);
+    mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_OUT);
+    mapper_admin_bundle_message(admin, ADM_DISCONNECTED, 0, "ss",
+                                src_name, dest_name);
 
     return 0;
 }
@@ -2313,8 +2874,8 @@ static int handler_signal_disconnected(const char *path, const char *types,
                                                           dest_name));
     }
 
-    if (!md || prefix_cmp(dest_name, mdev_name(md),
-                          &dest_signal_name))
+    if (!md || !mdev_name(md) || prefix_cmp(dest_name, mdev_name(md),
+                                            &dest_signal_name))
         return 0;
 
     src_signal_name = strchr(src_name+1, '/');
@@ -2351,6 +2912,11 @@ static int handler_signal_disconnected(const char *path, const char *types,
         md->connection_cb(md, &r->props, sig, &c->props, MDEV_LOCAL_DESTROYED,
                           md->connection_cb_userdata);
 
+    // Inform subscribers
+    mapper_admin_set_bundle_dest_subscribers(admin, SUB_DEVICE_CONNECTIONS_IN);
+    mapper_admin_bundle_message(admin, ADM_DISCONNECTED, 0, "ss",
+                                src_name, dest_name);
+
     /* The connection is removed. */
     if (mapper_receiver_remove_connection(r, c)) {
         return 0;
@@ -2358,125 +2924,106 @@ static int handler_signal_disconnected(const char *path, const char *types,
     return 0;
 }
 
-/*! Report existing connections to the network */
-static int handler_device_connections_get(const char *path,
-                                          const char *types,
-                                          lo_arg **argv, int argc,
-                                          lo_message msg, void *user_data)
-{
-    handler_device_connections_in_get(path, types, argv, argc, msg, user_data);
-    handler_device_connections_out_get(path, types, argv, argc, msg, user_data);
-
-    return 0;
-}
-
-/*! Report existing incoming connections to the network */
-static int handler_device_connections_in_get(const char *path,
-                                             const char *types,
-                                             lo_arg **argv, int argc,
-                                             lo_message msg, void *user_data)
+static int handler_device_link_ping(const char *path,
+                                    const char *types,
+                                    lo_arg **argv, int argc,
+                                    lo_message msg, void *user_data)
 {
+    // TODO: use only one ping between devices (bidirectional links?)
+    // message args: remote_device_hash, message_id, last_message_id, elapsed_time
     mapper_admin admin = (mapper_admin) user_data;
     mapper_device md = admin->device;
-    mapper_router receiver = md->receivers;
-    int i = 0, min = -1, max = -1;
-
-    trace("<%s> got /connections/get\n", mdev_name(md));
+    mapper_clock_t *clock = &admin->clock;
 
-    if (!argc && (md->flags & FLAGS_SENT_DEVICE_CONNECTIONS_IN))
+    if (!md)
         return 0;
 
-    if (argc > 0) {
-        if (types[0] == 'i')
-            min = argv[0]->i;
-        else if (types[0] == 'f')
-            min = (int)argv[0]->f;
-        if (min < 0)
-            min = 0;
-    }
-    if (argc > 1) {
-        if (types[1] == 'i')
-            max = argv[1]->i;
-        else if (types[1] == 'f')
-            max = (int)argv[1]->f;
-        if (max < min)
-            max = min + 1;
-    }
+    mapper_timetag_t now;
+    mapper_clock_now(clock, &now);
+    lo_timetag then = lo_message_get_timestamp(msg);
 
-    while (receiver) {
-        mapper_receiver_signal rs = receiver->signals;
-        while (rs) {
-			mapper_connection c = rs->connections;
-            while (c) {
-                if (max > 0 && i > max)
-                    break;
-                if (i >= min) {
-                    mapper_admin_send_connected(admin, receiver, c, i, 0);
+    mapper_router router = mapper_router_find_by_dest_hash(md->routers,
+                                                           argv[0]->i);
+    if (router) {
+        if (argv[2]->i == router->clock.sent.message_id) {
+            // total elapsed time since ping sent
+            double elapsed = mapper_timetag_difference(now, router->clock.sent.timetag);
+            // assume symmetrical latency
+            double latency = (elapsed - argv[3]->d) * 0.5;
+            // difference between remote and local clocks (latency compensated)
+            double offset = mapper_timetag_difference(now, then) - latency;
+
+            if (latency < 0) {
+                trace("error: latency cannot be < 0");
+                return 0;
+            }
+
+            if (router->clock.new == 1) {
+                router->clock.offset = offset;
+                router->clock.latency = latency;
+                router->clock.jitter = 0;
+                router->clock.new = 0;
+            }
+            else {
+                router->clock.jitter = router->clock.jitter * 0.9 + fabs(router->clock.latency - latency) * 0.1;
+                if (offset > router->clock.offset) {
+                    // remote timetag is in the future
+                    router->clock.offset = offset;
+                }
+                else if (latency < router->clock.latency + router->clock.jitter
+                         && latency > router->clock.latency - router->clock.jitter) {
+                    router->clock.offset = router->clock.offset * 0.9 + offset * 0.1;
+                    router->clock.latency = router->clock.latency * 0.9 + latency * 0.1;
                 }
-                c = c->next;
-                i++;
             }
-            rs = rs->next;
         }
-        receiver = receiver->next;
-    }
-
-    md->flags |= FLAGS_SENT_DEVICE_CONNECTIONS_IN;
-    return 0;
-}
-
-/*! Report existing outgoing connections to the network */
-static int handler_device_connections_out_get(const char *path,
-                                              const char *types,
-                                              lo_arg **argv, int argc,
-                                              lo_message msg, void *user_data)
-{
-    mapper_admin admin = (mapper_admin) user_data;
-    mapper_device md = admin->device;
-    mapper_router router = md->routers;
-    int i = 0, min = -1, max = -1;
 
-    trace("<%s> got /connections/get\n", mdev_name(md));
-
-    if (!argc && (md->flags & FLAGS_SENT_DEVICE_CONNECTIONS_OUT))
-        return 0;
-
-    if (argc > 0) {
-        if (types[0] == 'i')
-            min = argv[0]->i;
-        else if (types[0] == 'f')
-            min = (int)argv[0]->f;
-        if (min < 0)
-            min = 0;
-    }
-    if (argc > 1) {
-        if (types[1] == 'i')
-            max = argv[1]->i;
-        else if (types[1] == 'f')
-            max = (int)argv[1]->f;
-        if (max < min)
-            max = min + 1;
+        // update sync status
+        mapper_timetag_cpy(&router->clock.response.timetag, now);
+        router->clock.response.message_id = argv[1]->i;
     }
 
-    while (router) {
-        mapper_router_signal rs = router->signals;
-        while (rs) {
-			mapper_connection c = rs->connections;
-            while (c) {
-                if (max > 0 && i > max)
-                    break;
-                if (i >= min) {
-                    mapper_admin_send_connected(admin, router, c, i, 1);
+    mapper_receiver receiver =
+        mapper_receiver_find_by_src_hash(md->receivers, argv[0]->i);
+    if (receiver) {
+        if (argv[2]->i == receiver->clock.sent.message_id) {
+            // total elapsed time since ping sent
+            double elapsed = mapper_timetag_difference(now, receiver->clock.sent.timetag);
+            // assume symmetrical latency
+            double latency = (elapsed - argv[3]->d) * 0.5;
+            // difference between remote and local clocks (latency compensated)
+            double offset = mapper_timetag_difference(now, then) - latency;
+
+            if (latency < 0) {
+                trace("error: latency cannot be < 0");
+                return 0;
+            }
+
+            if (receiver->clock.new == 1) {
+                receiver->clock.offset = offset;
+                receiver->clock.latency = latency;
+                receiver->clock.jitter = 0;
+                receiver->clock.new = 0;
+            }
+            else {
+                receiver->clock.jitter = receiver->clock.jitter * 0.9 + fabs(receiver->clock.latency - latency) * 0.1;
+                if (offset > receiver->clock.offset) {
+                    // remote timetag is in the future
+                    receiver->clock.offset = offset;
+                }
+                else if (latency < receiver->clock.latency + receiver->clock.jitter
+                         && latency > receiver->clock.latency - receiver->clock.jitter) {
+                    receiver->clock.offset = receiver->clock.offset * 0.9 + offset * 0.1;
+                    receiver->clock.latency = receiver->clock.latency * 0.9 + latency * 0.1;
                 }
-                c = c->next;
-                i++;
             }
-            rs = rs->next;
         }
-        router = router->next;
+
+        // update sync status
+        mapper_timetag_cpy(&receiver->clock.response.timetag, now);
+        receiver->clock.response.message_id = argv[1]->i;
     }
 
-    md->flags |= FLAGS_SENT_DEVICE_CONNECTIONS_OUT;
     return 0;
 }
 
@@ -2486,58 +3033,24 @@ static int handler_sync(const char *path,
                         lo_message msg, void *user_data)
 {
     mapper_admin admin = (mapper_admin) user_data;
-    mapper_device md = admin->device;
     mapper_monitor mon = admin->monitor;
-    mapper_clock_t *clock = &admin->clock;
 
-    int device_id = argv[0]->i;
-    // if I sent this message, ignore it
-    if (md && (device_id == 0 || device_id == mdev_id(md)))
+    if (!mon || !argc)
         return 0;
 
-    int message_id = argv[1]->i;
-
-    // get current time
-    mapper_timetag_t now;
-    mapper_clock_now(clock, &now);
-
-    // store remote timetag
-    clock->remote.device_id = device_id;
-    clock->remote.message_id = message_id;
-    clock->remote.timetag.sec = now.sec;
-    clock->remote.timetag.frac = now.frac;
-
-    lo_timetag then = lo_message_get_timestamp(msg);
-    float confidence = argv[2]->f;
-
-    if (mon) {
-        mapper_db_device reg = mapper_db_get_device_by_name_hash(&mon->db,
-                                                                 device_id);
-        if (reg)
-            mapper_timetag_cpy(&reg->synced, then);
+    mapper_db_device reg = 0;
+    if (types[0] == 's' || types[0] == 'S') {
+        if ((reg = mapper_db_get_device_by_name(&mon->db, &argv[0]->s)))
+            mapper_timetag_cpy(&reg->synced, lo_message_get_timestamp(msg));
+        else if (mon->autosubscribe) {
+            // only create device record after requesting more information
+            mapper_monitor_subscribe(mon, &argv[0]->s, mon->autosubscribe, -1);
+        }
+    }
+    else if (types[0] == 'i') {
+        if ((reg = mapper_db_get_device_by_name_hash(&mon->db, argv[0]->i)))
+            mapper_timetag_cpy(&reg->synced, lo_message_get_timestamp(msg));
     }
-
-    // if remote timetag is in the future, adjust to remote time
-    double diff = mapper_timetag_difference(then, now);
-    mapper_clock_adjust(&admin->clock, diff, 1.0);
-
-    if (!md)
-        return 0;
-
-    // look at the second part of the message
-    device_id = argv[3]->i;
-    if (device_id != mdev_id(md))
-        return 0;
-
-    message_id = argv[4]->i;
-    if (message_id >= 10)
-        return 0;
-
-    // Calculate latency on exchanged /sync messages
-    double latency = (mapper_timetag_difference(now, clock->local[message_id].timetag)
-                      - argv[5]->d) * 0.5;
-    if (latency > 0 && latency < 100)
-        mapper_clock_adjust(&admin->clock, diff + latency, confidence);
 
     return 0;
 }
diff --git a/src/connection.c b/src/connection.c
index cc3044a..dc30cef 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -13,11 +13,6 @@ static void reallocate_connection_histories(mapper_connection c,
                                             int input_history_size,
                                             int output_history_size);
 
-static void mhist_realloc(mapper_signal_history_t *history,
-                          int history_size,
-                          int sample_size,
-                          int is_output);
-
 const char* mapper_boundary_action_strings[] =
 {
     "none",        /* BA_NONE */
@@ -57,7 +52,9 @@ const char *mapper_get_mode_type_string(mapper_mode_type mode)
 
 int mapper_connection_perform(mapper_connection connection,
                               mapper_signal_history_t *from,
-                              mapper_signal_history_t *to)
+                              mapper_signal_history_t **expr_vars,
+                              mapper_signal_history_t *to,
+                              char *typestring)
 {
     int changed = 0, i;
     int vector_length = from->length < to->length ? from->length : to->length;
@@ -150,15 +147,18 @@ int mapper_connection_perform(mapper_connection connection,
                 }
             }
         }
+        for (i = 0; i < vector_length; i++) {
+            typestring[i] = to->type;
+        }
         return 1;
     }
     else if (connection->props.mode == MO_EXPRESSION
              || connection->props.mode == MO_LINEAR)
     {
         die_unless(connection->expr!=0, "Missing expression.\n");
-        return (mapper_expr_evaluate(connection->expr, from, to));
+        return (mapper_expr_evaluate(connection->expr, from, expr_vars,
+                                     to, typestring));
     }
-
     else if (connection->props.mode == MO_CALIBRATE)
     {
         /* Increment index position of output data structure. */
@@ -261,7 +261,8 @@ int mapper_connection_perform(mapper_connection connection,
         }
 
         if (connection->expr)
-            return (mapper_expr_evaluate(connection->expr, from, to));
+            return (mapper_expr_evaluate(connection->expr, from, expr_vars,
+                                         to, typestring));
         else
             return 0;
     }
@@ -508,12 +509,14 @@ static int replace_expression_string(mapper_connection c,
 {
     mapper_expr expr = mapper_expr_new_from_string(
         expr_str, c->props.src_type, c->props.dest_type,
-        c->props.src_length, c->props.dest_length,
-        input_history_size, output_history_size);
+        c->props.src_length, c->props.dest_length);
 
     if (!expr)
         return 1;
 
+    *input_history_size = mapper_expr_input_history_size(expr);
+    *output_history_size = mapper_expr_output_history_size(expr);
+
     if (c->expr)
         mapper_expr_free(c->expr);
 
@@ -542,82 +545,49 @@ void mapper_connection_set_mode_direct(mapper_connection c)
 
 void mapper_connection_set_mode_linear(mapper_connection c)
 {
-    int i;
+    int i, len;
     char expr[256] = "";
     const char *e = expr;
 
     if (c->props.range_known != CONNECTION_RANGE_KNOWN)
         return;
 
-    if (c->props.dest_length == 1) {
-        if (memcmp(c->props.src_min, c->props.src_max,
-                   mapper_type_size(c->props.src_type))==0) {
-            // set value to constant to avoid division by zero
-            if (c->props.src_type == 'f') {
-                float *temp = (float*)c->props.dest_min;
-                snprintf(expr, 256, "y=%g", temp[0]);
-            }
-            else if (c->props.src_type == 'i') {
-                int *temp = (int*)c->props.dest_min;
-                snprintf(expr, 256, "y=%i", temp[0]);
-            }
-            else if (c->props.src_type == 'd') {
-                double *temp = (double*)c->props.dest_min;
-                snprintf(expr, 256, "y=%g", temp[0]);
-            }
-        }
-        else {
-            double src_min, src_max, dest_min, dest_max;
-            if (c->props.src_length > 1)
-                snprintf(expr, 256, "y=x[0]");
-            else
-                snprintf(expr, 256, "y=x");
-
-            src_min = propval_get_double(c->props.src_min, c->props.src_type, 0);
-            src_max = propval_get_double(c->props.src_max, c->props.src_type, 0);
-            dest_min = propval_get_double(c->props.dest_min, c->props.dest_type, 0);
-            dest_max = propval_get_double(c->props.dest_max, c->props.dest_type, 0);
+    int min_length = c->props.src_length < c->props.dest_length ?
+                     c->props.src_length : c->props.dest_length;
+    double src_min, src_max, dest_min, dest_max;
 
-            if ((src_min != dest_min) || (src_max != dest_max)) {
-                double scale = ((dest_min - dest_max) / (src_min - src_max));
-                double offset = ((dest_max * src_min - dest_min * src_max)
-                                 / (src_min - src_max));
-                snprintf(expr+strlen(expr), 256, "*(%g)+(%g)", scale, offset);
-            }
-        }
+    if (c->props.dest_length == c->props.src_length)
+        snprintf(expr, 256, "y=x*");
+    else if (c->props.dest_length > c->props.src_length) {
+        if (min_length == 1)
+            snprintf(expr, 256, "y[0]=x*");
+        else
+            snprintf(expr, 256, "y[0:%i]=x*", min_length-1);
     }
     else {
-        int len, diff;
-        int min_length = c->props.src_length < c->props.dest_length ?
-                         c->props.src_length : c->props.dest_length;
-        double src_min, src_max, dest_min, dest_max;
-
-        if (c->props.src_length == c->props.dest_length)
-            snprintf(expr, 256, "y=x*[");
-        else if (c->props.src_length > c->props.dest_length)
-            snprintf(expr, 256, "y=x[0:%i]*[", c->props.dest_length-1);
-        else {
-            diff = c->props.dest_length - c->props.src_length;
-            snprintf(expr, 256, "y=[x,");
-            while (diff--) {
-                len = strlen(expr);
-                snprintf(expr+len, 256-len, "0,");
-            }
-            len = strlen(expr);
-            snprintf(expr+len-1, 256-len+1, "]*[");
-        }
+        if (min_length == 1)
+            snprintf(expr, 256, "y=x[0]*");
+        else
+            snprintf(expr, 256, "y=x[0:%i]*", min_length-1);
+    }
+
+    if (min_length > 1) {
+        len = strlen(expr);
+        snprintf(expr+len, 256-len, "[");
+    }
 
-        // add scale
-        for (i=0; i<min_length; i++) {
-            src_min = propval_get_double(c->props.src_min, c->props.src_type, i);
-            src_max = propval_get_double(c->props.src_max, c->props.src_type, i);
+    for (i = 0; i < min_length; i++) {
+        // get multiplier
+        src_min = propval_get_double(c->props.src_min, c->props.src_type, i);
+        src_max = propval_get_double(c->props.src_max, c->props.src_type, i);
+
+        len = strlen(expr);
+        if (src_min == src_max)
+            snprintf(expr+len, 256-len, "0,");
+        else {
             dest_min = propval_get_double(c->props.dest_min, c->props.dest_type, i);
             dest_max = propval_get_double(c->props.dest_max, c->props.dest_type, i);
-
-            len = strlen(expr);
-            if (src_min == src_max)
-                snprintf(expr+len, 256-len, "0,");
-            else if ((src_min == dest_min) && (src_max == dest_max)) {
+            if ((src_min == dest_min) && (src_max == dest_max)) {
                 snprintf(expr+len, 256-len, "1,");
             }
             else {
@@ -625,25 +595,25 @@ void mapper_connection_set_mode_linear(mapper_connection c)
                 snprintf(expr+len, 256-len, "%g,", scale);
             }
         }
-        diff = c->props.dest_length - c->props.src_length;
-        while (diff--) {
-            len = strlen(expr);
-            snprintf(expr+len, 256-len, "0,");
-        }
-        len = strlen(expr);
+    }
+    len = strlen(expr);
+    if (min_length > 1)
         snprintf(expr+len-1, 256-len+1, "]+[");
+    else
+        snprintf(expr+len-1, 256-len+1, "+");
 
-        // add offset
-        for (i=0; i<min_length; i++) {
-            src_min = propval_get_double(c->props.src_min, c->props.src_type, i);
-            src_max = propval_get_double(c->props.src_max, c->props.src_type, i);
+    // add offset
+    for (i=0; i<min_length; i++) {
+        src_min = propval_get_double(c->props.src_min, c->props.src_type, i);
+        src_max = propval_get_double(c->props.src_max, c->props.src_type, i);
+
+        len = strlen(expr);
+        if (src_min == src_max)
+            snprintf(expr+len, 256-len, "%g,", dest_min);
+        else {
             dest_min = propval_get_double(c->props.dest_min, c->props.dest_type, i);
             dest_max = propval_get_double(c->props.dest_max, c->props.dest_type, i);
-
-            len = strlen(expr);
-            if (src_min == src_max)
-                snprintf(expr+len, 256-len, "%g,", dest_min);
-            else if ((src_min == dest_min) && (src_max == dest_max)) {
+            if ((src_min == dest_min) && (src_max == dest_max)) {
                 snprintf(expr+len, 256-len, "0,");
             }
             else {
@@ -652,14 +622,12 @@ void mapper_connection_set_mode_linear(mapper_connection c)
                 snprintf(expr+len, 256-len, "%g,", offset);
             }
         }
-        diff = c->props.dest_length - c->props.src_length;
-        while (diff--) {
-            len = strlen(expr);
-            snprintf(expr+len, 256-len, "0,");
-        }
-        len = strlen(expr);
-        snprintf(expr+len-1, 256-len+1, "]");
     }
+    len = strlen(expr);
+    if (min_length > 1)
+        snprintf(expr+len-1, 256-len+1, "]");
+    else
+        expr[len-1] = '\0';
 
     // If everything is successful, replace the connection's expression.
     if (e) {
@@ -688,7 +656,8 @@ void mapper_connection_set_mode_expression(mapper_connection c,
     /* TODO: should call handler for all instances updated
      * through this connection. */
     mapper_signal sig = c->parent->signal;
-    if (!sig->props.is_output && mapper_expr_constant_output(c->expr)) {
+    if (!sig->props.is_output && mapper_expr_constant_output(c->expr)
+        && !c->props.send_as_instance) {
         int index = 0;
         mapper_timetag_t now;
         mapper_clock_now(&sig->device->admin->clock, &now);
@@ -705,7 +674,9 @@ void mapper_connection_set_mode_expression(mapper_connection c,
         h.position = -1;
         h.length = sig->props.length;
         h.size = 1;
-        mapper_expr_evaluate(c->expr, 0, &h);
+        char typestring[h.length];
+        mapper_expr_evaluate(c->expr, 0, &c->expr_vars[si->index],
+                             &h, typestring);
 
         // call handler if it exists
         if (sig->handler)
@@ -727,27 +698,38 @@ void mapper_connection_set_mode_calibrate(mapper_connection c)
 
     char expr[256];
     int i, len;
+    int min_length = c->props.src_length < c->props.dest_length ?
+                     c->props.src_length : c->props.dest_length;
 
-    if (c->props.dest_length > 1)
-        snprintf(expr, 256, "y=[");
+    if (c->props.dest_length > c->props.src_length) {
+        if (min_length == 1)
+            snprintf(expr, 256, "y[0]=");
+        else
+            snprintf(expr, 256, "y[0:%i]=", min_length-1);
+    }
     else
         snprintf(expr, 256, "y=");
 
-    if (c->props.src_type == 'f') {
+    if (c->props.dest_length > 1) {
+        len = strlen(expr);
+        snprintf(expr+len, 256-len, "[");
+    }
+
+    if (c->props.dest_type == 'f') {
         float *temp = (float*)c->props.dest_min;
         for (i=0; i<c->props.dest_length; i++) {
             len = strlen(expr);
             snprintf(expr+len, 256-len, "%g,", temp[i]);
         }
     }
-    else if (c->props.src_type == 'i') {
+    else if (c->props.dest_type == 'i') {
         int *temp = (int*)c->props.dest_min;
         for (i=0; i<c->props.dest_length; i++) {
             len = strlen(expr);
             snprintf(expr+len, 256-len, "%i,", temp[i]);
         }
     }
-    else if (c->props.src_type == 'd') {
+    else if (c->props.dest_type == 'd') {
         double *temp = (double*)c->props.dest_min;
         for (i=0; i<c->props.dest_length; i++) {
             len = strlen(expr);
@@ -1033,13 +1015,13 @@ int mapper_connection_set_from_message(mapper_connection c,
         int input_history_size, output_history_size;
         if (!replace_expression_string(c, expr, &input_history_size,
                                        &output_history_size)) {
-            if (c->props.mode == MO_EXPRESSION)
+            if (c->props.mode == MO_EXPRESSION) {
                 reallocate_connection_histories(c, input_history_size,
                                                 output_history_size);
+            }
         }
         updated++;
     }
-
     /* Instances. */
     int send_as_instance;
     if (!mapper_msg_get_param_if_int(msg, AT_SEND_AS_INSTANCE, &send_as_instance)
@@ -1093,18 +1075,20 @@ int mapper_connection_set_from_message(mapper_connection c,
                 else {
                     char expr[256] = "";
                     if (c->props.src_length > c->props.dest_length) {
-                        // truncate
-                        snprintf(expr, 256, "y=x[0:%i]", c->props.dest_length-1);
+                        // truncate source
+                        if (c->props.dest_length == 1)
+                            snprintf(expr, 256, "y=x[0]");
+                        else
+                            snprintf(expr, 256, "y=x[0:%i]",
+                                     c->props.dest_length-1);
                     }
                     else {
-                        // zero-pad
-                        int diff = c->props.dest_length - c->props.src_length;
-                        snprintf(expr, 256, "y=[x,");
-                        while (diff--) {
-                            int len = strlen(expr);
-                            snprintf(expr+len, 256-len, "0,");
-                        }
-                        expr[strlen(expr)-1] = ']';
+                        // truncate destination
+                        if (c->props.src_length == 1)
+                            snprintf(expr, 256, "y[0]=x");
+                        else
+                            snprintf(expr, 256, "y[0:%i]=x",
+                                     c->props.src_length);
                     }
                     c->props.expression = strdup(expr);
                 }
@@ -1119,65 +1103,20 @@ int mapper_connection_set_from_message(mapper_connection c,
         trace("unknown result from mapper_msg_get_mode()\n");
         break;
     }
-
     return updated;
 }
 
-mapper_connection mapper_connection_find_by_names(mapper_device md,
-                                                  const char* src_name,
-                                                  const char* dest_name)
-{
-    mapper_router router = md->routers;
-    int i = 0;
-    int n = strlen(dest_name);
-    const char *slash = strchr(dest_name+1, '/');
-    if (slash)
-        n = n - strlen(slash);
-
-    src_name = strchr(src_name+1, '/');
-
-    while (i < md->props.n_outputs) {
-        // Check if device outputs includes src_name
-        if (strcmp(md->outputs[i]->props.name, src_name) == 0) {
-            while (router != NULL) {
-                // find associated router
-                if (strncmp(router->props.dest_name, dest_name, n) == 0) {
-                    // find associated connection
-                    mapper_router_signal rs = router->signals;
-                    while (rs && rs->signal != md->outputs[i])
-                        rs = rs->next;
-                    if (!rs)
-                        return NULL;
-                    mapper_connection c = rs->connections;
-                    while (c && strcmp(c->props.dest_name,
-                                       (dest_name + n)) != 0)
-                        c = c->next;
-                    if (!c)
-                        return NULL;
-                    else
-                        return c;
-                }
-                else {
-                    router = router->next;
-                }
-            }
-            return NULL;
-        }
-        i++;
-    }
-    return NULL;
-}
-
 void reallocate_connection_histories(mapper_connection c,
                                      int input_history_size,
                                      int output_history_size)
 {
     mapper_signal sig = c->parent->signal;
-    int i;
+    int i, j;
 
     // At least for now, exit if this is an input signal
-    if (!sig->props.is_output)
+    if (!sig->props.is_output) {
         return;
+    }
 
     // If there is no expression, then no memory needs to be
     // reallocated.
@@ -1187,11 +1126,12 @@ void reallocate_connection_histories(mapper_connection c,
     if (input_history_size < 1)
         input_history_size = 1;
 
+    // Reallocate input histories
     if (input_history_size > c->parent->history_size) {
-        int sample_size = msig_vector_bytes(sig);
+        size_t sample_size = msig_vector_bytes(sig);
         for (i=0; i<sig->props.num_instances; i++) {
             mhist_realloc(&c->parent->history[i], input_history_size,
-                          sample_size, 0);
+                          sample_size, 1);
         }
         c->parent->history_size = input_history_size;
     }
@@ -1208,32 +1148,67 @@ void reallocate_connection_histories(mapper_connection c,
             c = c->next;
         }*/
     }
+
+    // reallocate output histories
     if (output_history_size > c->props.dest_history_size) {
         int sample_size = mapper_type_size(c->props.dest_type) * c->props.dest_length;
         for (i=0; i<sig->props.num_instances; i++) {
-            mhist_realloc(&c->history[i], output_history_size, sample_size, 1);
+            mhist_realloc(&c->history[i], output_history_size, sample_size, 0);
         }
         c->props.dest_history_size = output_history_size;
     }
     else if (output_history_size < mapper_expr_output_history_size(c->expr)) {
         // Do nothing for now...
     }
+
+    // reallocate user variable histories
+    int new_num_vars = mapper_expr_num_variables(c->expr);
+    if (new_num_vars > c->num_expr_vars) {
+        for (i=0; i<sig->props.num_instances; i++) {
+            c->expr_vars[i] = realloc(c->expr_vars[i], new_num_vars *
+                                      sizeof(struct _mapper_signal_history));
+            // initialize new variables...
+            for (j=c->num_expr_vars; j<new_num_vars; j++) {
+                c->expr_vars[i][j].type = 'd';
+                c->expr_vars[i][j].length = 0;
+                c->expr_vars[i][j].size = 0;
+                c->expr_vars[i][j].value = 0;
+                c->expr_vars[i][j].timetag = 0;
+                c->expr_vars[i][j].position = -1;
+            }
+        }
+        c->num_expr_vars = new_num_vars;
+    }
+    else if (new_num_vars < c->num_expr_vars) {
+        // Do nothing for now...
+    }
+    for (i=0; i<sig->props.num_instances; i++) {
+        for (j=0; j<new_num_vars; j++) {
+            int history_size = mapper_expr_variable_history_size(c->expr, j);
+            int vector_length = mapper_expr_variable_vector_length(c->expr, j);
+            mhist_realloc(c->expr_vars[i]+j, history_size,
+                          vector_length * sizeof(double), 0);
+            (c->expr_vars[i]+j)->length = vector_length;
+            (c->expr_vars[i]+j)->size = history_size;
+            (c->expr_vars[i]+j)->position = -1;
+        }
+    }
 }
 
 void mhist_realloc(mapper_signal_history_t *history,
                    int history_size,
                    int sample_size,
-                   int is_output)
+                   int is_input)
 {
     if (!history || !history_size || !sample_size)
         return;
     if (history_size == history->size)
         return;
-    if (is_output || (history_size > history->size) || (history->position == 0)) {
+    if (!is_input || (history_size > history->size) || (history->position == 0)) {
         // realloc in place
         history->value = realloc(history->value, history_size * sample_size);
         history->timetag = realloc(history->timetag, history_size * sizeof(mapper_timetag_t));
-        if (is_output) {
+        if (!is_input) {
             // Initialize entire history to 0
             memset(history->value, 0, history_size * sample_size);
             history->position = -1;
diff --git a/src/db.c b/src/db.c
index 06b401c..2134bd7 100644
--- a/src/db.c
+++ b/src/db.c
@@ -529,13 +529,14 @@ static mapper_string_table_t sigdb_table =
 
 static property_table_value_t devdb_values[] = {
     { 's', {1}, -1, DEVDB_OFFSET(host) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_connections_in) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_connections_out) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_inputs) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_links_in) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_links_out) },
-    { 'i', {0}, -1, DEVDB_OFFSET(n_outputs) },
+    { 's', {1}, -1, DEVDB_OFFSET(lib_version) },
     { 's', {1}, -1, DEVDB_OFFSET(name) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_connections_in) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_connections_out) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_inputs) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_links_in) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_links_out) },
+    { 'i', {0}, -1, DEVDB_OFFSET(num_outputs) },
     { 'i', {0}, -1, DEVDB_OFFSET(port) },
     { 't', {0}, -1, DEVDB_OFFSET(synced) },
     { 'i', {0},  0, DEVDB_OFFSET(user_data) },
@@ -544,22 +545,23 @@ static property_table_value_t devdb_values[] = {
 
 /* This table must remain in alphabetical order. */
 static string_table_node_t devdb_nodes[] = {
-    { "host",               &devdb_values[0] },
-    { "n_connections_in",   &devdb_values[1] },
-    { "n_connections_out",  &devdb_values[2] },
-    { "n_inputs",           &devdb_values[3] },
-    { "n_links_in",         &devdb_values[4] },
-    { "n_links_out",        &devdb_values[5] },
-    { "n_outputs",          &devdb_values[6] },
-    { "name",               &devdb_values[7] },
-    { "port",               &devdb_values[8] },
-    { "synced",             &devdb_values[9] },
-    { "user_data",          &devdb_values[10] },
-    { "version",            &devdb_values[11] },
+    { "host",                &devdb_values[0] },
+    { "lib_version",         &devdb_values[1] },
+    { "name",                &devdb_values[2] },
+    { "num_connections_in",  &devdb_values[3] },
+    { "num_connections_out", &devdb_values[4] },
+    { "num_inputs",          &devdb_values[5] },
+    { "num_links_in",        &devdb_values[6] },
+    { "num_links_out",       &devdb_values[7] },
+    { "num_outputs",         &devdb_values[8] },
+    { "port",                &devdb_values[9] },
+    { "synced",              &devdb_values[10] },
+    { "user_data",           &devdb_values[11] },
+    { "version",             &devdb_values[12] },
 };
 
 static mapper_string_table_t devdb_table =
-  { devdb_nodes, 12, 12 };
+  { devdb_nodes, 13, 13 };
 
 static property_table_value_t linkdb_values[] = {
     { 's', {1}, -1,         LINKDB_OFFSET(dest_host) },
@@ -772,7 +774,8 @@ static int mapper_db_property_lookup(void *thestruct, table extra,
  *  parameters. */
 static int update_device_record_params(mapper_db_device reg,
                                        const char *name,
-                                       mapper_message_t *params)
+                                       mapper_message_t *params,
+                                       mapper_timetag_t *current_time)
 {
     int updated = 0;
 
@@ -780,21 +783,31 @@ static int update_device_record_params(mapper_db_device reg,
     if (updated)
         reg->name_hash = crc32(0L, (const Bytef *)name, strlen(name));
 
+    if (current_time)
+        mapper_timetag_cpy(&reg->synced, *current_time);
+
+    if (!params)
+        return updated;
+
     updated += update_string_if_arg(&reg->host, params, AT_IP);
 
+    updated += update_string_if_arg(&reg->lib_version, params, AT_LIB_VERSION);
+
     updated += update_int_if_arg(&reg->port, params, AT_PORT);
 
-    updated += update_int_if_arg(&reg->n_inputs, params, AT_NUM_INPUTS);
+    updated += update_int_if_arg(&reg->num_inputs, params, AT_NUM_INPUTS);
 
-    updated += update_int_if_arg(&reg->n_outputs, params, AT_NUM_OUTPUTS);
+    updated += update_int_if_arg(&reg->num_outputs, params, AT_NUM_OUTPUTS);
 
-    updated += update_int_if_arg(&reg->n_links_in, params, AT_NUM_LINKS_IN);
+    updated += update_int_if_arg(&reg->num_links_in, params, AT_NUM_LINKS_IN);
 
-    updated += update_int_if_arg(&reg->n_links_out, params, AT_NUM_LINKS_OUT);
+    updated += update_int_if_arg(&reg->num_links_out, params, AT_NUM_LINKS_OUT);
 
-    updated += update_int_if_arg(&reg->n_connections_in, params, AT_NUM_CONNECTIONS_IN);
+    updated += update_int_if_arg(&reg->num_connections_in, params,
+                                 AT_NUM_CONNECTIONS_IN);
 
-    updated += update_int_if_arg(&reg->n_connections_out, params, AT_NUM_CONNECTIONS_OUT);
+    updated += update_int_if_arg(&reg->num_connections_out, params,
+                                 AT_NUM_CONNECTIONS_OUT);
 
     updated += update_int_if_arg(&reg->version, params, AT_REV);
 
@@ -805,7 +818,8 @@ static int update_device_record_params(mapper_db_device reg,
 
 int mapper_db_add_or_update_device_params(mapper_db db,
                                           const char *name,
-                                          mapper_message_t *params)
+                                          mapper_message_t *params,
+                                          mapper_timetag_t *current_time)
 {
     mapper_db_device reg = mapper_db_get_device_by_name(db, name);
     int rc = 0, updated = 0;
@@ -819,7 +833,7 @@ int mapper_db_add_or_update_device_params(mapper_db db,
     }
 
     if (reg) {
-        updated = update_device_record_params(reg, name, params);
+        updated = update_device_record_params(reg, name, params, current_time);
 
         if (rc || updated) {
             fptr_list cb = db->device_callbacks;
@@ -849,40 +863,52 @@ int mapper_db_device_property_lookup(mapper_db_device dev, const char *property,
                                      value, length, &devdb_table);
 }
 
-void mapper_db_remove_device_by_name(mapper_db db, const char *name)
+static void db_remove_device_internal(mapper_db db, mapper_db_device dev,
+                                      int quiet)
 {
-    mapper_db_device dev = mapper_db_get_device_by_name(db, name);
     if (!dev)
         return;
 
     mapper_db_remove_connections_by_query(db,
-        mapper_db_get_connections_by_device_name(db, name));
+        mapper_db_get_connections_by_device_name(db, dev->name));
 
     mapper_db_remove_links_by_query(db,
-        mapper_db_get_links_by_device_name(db, name));
+        mapper_db_get_links_by_device_name(db, dev->name));
 
     mapper_db_remove_inputs_by_query(db,
-        mapper_db_get_inputs_by_device_name(db, name));
+        mapper_db_get_inputs_by_device_name(db, dev->name));
 
     mapper_db_remove_outputs_by_query(db,
-        mapper_db_get_outputs_by_device_name(db, name));
+        mapper_db_get_outputs_by_device_name(db, dev->name));
 
-    fptr_list cb = db->device_callbacks;
-    while (cb) {
-        mapper_db_device_handler *h = cb->f;
-        h(dev, MDB_REMOVE, cb->context);
-        cb = cb->next;
+    if (!quiet) {
+        fptr_list cb = db->device_callbacks;
+        while (cb) {
+            mapper_db_device_handler *h = cb->f;
+            h(dev, MDB_REMOVE, cb->context);
+            cb = cb->next;
+        }
     }
 
     if (dev->name)
         free(dev->name);
     if (dev->host)
         free(dev->host);
+    if (dev->lib_version)
+        free(dev->lib_version);
     if (dev->extra)
         table_free(dev->extra, 1);
     list_remove_item(dev, (void**)&db->registered_devices);
 }
 
+void mapper_db_remove_device_by_name(mapper_db db, const char *name)
+{
+    mapper_db_device dev = mapper_db_get_device_by_name(db, name);
+    if (!dev)
+        return;
+    db_remove_device_internal(db, dev, 0);
+}
+
 mapper_db_device mapper_db_get_device_by_name(mapper_db db,
                                               const char *name)
 {
@@ -961,81 +987,73 @@ void mapper_db_device_done(mapper_db_device_t **d)
 
 void mapper_db_dump(mapper_db db)
 {
+#ifdef DEBUG
     mapper_db_device reg = db->registered_devices;
-    trace("Registered devices:\n");
+    printf("Registered devices:\n");
     while (reg) {
-        trace("  name=%s, host=%s, port=%d\n",
-              reg->name, reg->host, reg->port);
+        printf("  name=%s, host=%s, port=%d\n", reg->name, reg->host, reg->port);
         reg = list_get_next(reg);
     }
 
     mapper_db_signal sig = db->registered_inputs;
-    trace("Registered inputs:\n");
+    printf("Registered inputs:\n");
     while (sig) {
-        trace("  name=%s%s\n",
-              sig->device_name, sig->name);
+        printf("  name=%s%s\n", sig->device_name, sig->name);
         sig = list_get_next(sig);
     }
 
     sig = db->registered_outputs;
-    trace("Registered outputs:\n");
+    printf("Registered outputs:\n");
     while (sig) {
-        trace("  name=%s%s\n",
-              sig->device_name, sig->name);
+        printf("  name=%s%s\n", sig->device_name, sig->name);
         sig = list_get_next(sig);
     }
 
+    mapper_db_link link = db->registered_links;
+    printf("Registered links:\n");
+    while (link) {
+        printf("  source=%s, dest=%s\n", link->src_name, link->dest_name);
+        link = list_get_next(link);
+    }
+
     mapper_db_connection con = db->registered_connections;
-    trace("Registered connections:\n");
+    printf("Registered connections:\n");
     while (con) {
-        char r[1024] = "(";
+        printf("  src_name=%s, dest_name=%s,\n"
+               "      src_type=%c, dest_type=%c,\n"
+               "      src_length=%d, dest_length=%d,\n"
+               "      bound_max=%s, bound_min=%s,\n"
+               "      expression=%s, mode=%s, muted=%d\n",
+               con->src_name, con->dest_name, con->src_type,
+               con->dest_type, con->src_length, con->dest_length,
+               mapper_get_boundary_action_string(con->bound_max),
+               mapper_get_boundary_action_string(con->bound_min),
+               con->expression,
+               mapper_get_mode_type_string(con->mode),
+               con->muted);
         if (con->range_known & CONNECTION_RANGE_SRC_MIN) {
+            printf("      src_min=");
             mapper_prop_pp(con->src_type, con->src_length, con->src_min);
-            sprintf(r+strlen(r), ", ");
+            printf("\n");
         }
-        else
-            strcat(r, "-, ");
         if (con->range_known & CONNECTION_RANGE_SRC_MAX) {
+            printf("      src_max=");
             mapper_prop_pp(con->src_type, con->src_length, con->src_max);
-            sprintf(r+strlen(r), ", ");
+            printf("\n");
         }
-        else
-            strcat(r, "-, ");
         if (con->range_known & CONNECTION_RANGE_DEST_MIN) {
+            printf("      dest_min=");
             mapper_prop_pp(con->dest_type, con->dest_length, con->dest_min);
-            sprintf(r+strlen(r), ", ");
+            printf("\n");
         }
-        else
-            strcat(r, "-, ");
         if (con->range_known & CONNECTION_RANGE_DEST_MAX) {
+            printf("      dest_max=");
             mapper_prop_pp(con->dest_type, con->dest_length, con->dest_max);
+            printf("\n");
         }
-        else
-            strcat(r, "-");
-        strcat(r, ")");
-        trace("  src_name=%s, dest_name=%s,\n"
-              "      src_type=%c, dest_type=%c,\n"
-              "      src_length=%d, dest_length=%d,\n"
-              "      bound_max=%s, bound_min=%s,\n"
-              "      range=%s,\n"
-              "      expression=%s, mode=%s, muted=%d\n",
-              con->src_name, con->dest_name, con->src_type,
-              con->dest_type, con->src_length, con->dest_length,
-              mapper_get_boundary_action_string(con->bound_max),
-              mapper_get_boundary_action_string(con->bound_min),
-              r, con->expression,
-              mapper_get_mode_type_string(con->mode),
-              con->muted);
         con = list_get_next(con);
     }
-
-    mapper_db_link link = db->registered_links;
-    trace("Registered links:\n");
-    while (link) {
-        trace("  source=%s, dest=%s\n",
-              link->src_name, link->dest_name);
-        link = list_get_next(link);
-    }
+#endif
 }
 
 void mapper_db_add_device_callback(mapper_db db,
@@ -1050,6 +1068,39 @@ void mapper_db_remove_device_callback(mapper_db db,
     remove_callback(&db->device_callbacks, h, user);
 }
 
+void mapper_db_check_device_status(mapper_db db, uint32_t thresh_time_sec)
+{
+    mapper_db_device reg = db->registered_devices;
+    while (reg) {
+        // check if device has "checked in" recently
+        // this could be /sync ping or any sent metadata
+        if (reg->synced.sec && (reg->synced.sec < thresh_time_sec)) {
+            fptr_list cb = db->device_callbacks;
+            while (cb) {
+                mapper_db_device_handler *h = cb->f;
+                h(reg, MDB_UNRESPONSIVE, cb->context);
+                cb = cb->next;
+            }
+        }
+        reg = list_get_next(reg);
+    }
+}
+
+int mapper_db_flush(mapper_db db, uint32_t current_time,
+                    uint32_t timeout, int quiet)
+{
+    mapper_db_device reg = db->registered_devices;
+    int removed = 0;
+    while (reg) {
+        if (reg->synced.sec && (current_time - reg->synced.sec > timeout)) {
+            db_remove_device_internal(db, reg, quiet);
+            removed++;
+        }
+        reg = list_get_next(reg);
+    }
+    return removed;
+}
+
 /**** Signals ****/
 
 /*! Update information about a given signal record based on message
@@ -1066,6 +1117,9 @@ static int update_signal_record_params(mapper_db_signal sig,
     updated += update_string_if_different((char**)&sig->name, name);
     updated += update_string_if_different((char**)&sig->device_name, device_name);
 
+    if (!params)
+        return updated;
+
     updated += update_int_if_arg(&sig->id, params, AT_ID);
 
     updated += update_char_if_arg(&sig->type, params, AT_TYPE);
@@ -1400,6 +1454,52 @@ void mapper_db_signal_done(mapper_db_signal_t **s)
         lh->query_context->query_free(lh);
 }
 
+static void mapper_db_remove_signal(mapper_db db, mapper_db_signal sig,
+                                    int is_output)
+{
+    fptr_list cb = db->signal_callbacks;
+    while (cb) {
+        mapper_db_signal_handler *h = cb->f;
+        h(sig, MDB_REMOVE, cb->context);
+        cb = cb->next;
+    }
+
+    if (sig->name)
+        free(sig->name);
+    if (sig->device_name)
+        free(sig->device_name);
+    if (sig->unit)
+        free(sig->unit);
+    if (sig->minimum)
+        free(sig->minimum);
+    if (sig->maximum)
+        free(sig->maximum);
+    if (sig->extra)
+        table_free(sig->extra, 1);
+    if (is_output)
+        list_remove_item(sig, (void**)&db->registered_outputs);
+    else
+        list_remove_item(sig, (void**)&db->registered_inputs);
+}
+
+void mapper_db_remove_input_by_name(mapper_db db, const char *device_name,
+                                    const char *signal_name)
+{
+    mapper_db_signal sig;
+    sig = mapper_db_get_input_by_device_and_signal_names(db, device_name,
+                                                         signal_name);
+
+    if (!sig)
+        return;
+
+    mapper_db_remove_connections_by_query(db,
+        mapper_db_get_connections_by_dest_device_and_signal_names(db,
+                                                                  device_name,
+                                                                  signal_name));
+
+    mapper_db_remove_signal(db, sig, 0);
+}
+
 void mapper_db_remove_inputs_by_query(mapper_db db,
                                       mapper_db_signal_t **s)
 {
@@ -1407,29 +1507,28 @@ void mapper_db_remove_inputs_by_query(mapper_db db,
         mapper_db_signal sig = *s;
         s = mapper_db_signal_next(s);
 
-        fptr_list cb = db->signal_callbacks;
-        while (cb) {
-            mapper_db_signal_handler *h = cb->f;
-            h(sig, MDB_REMOVE, cb->context);
-            cb = cb->next;
-        }
-
-        if (sig->name)
-            free(sig->name);
-        if (sig->device_name)
-            free(sig->device_name);
-        if (sig->unit)
-            free(sig->unit);
-        if (sig->minimum)
-            free(sig->minimum);
-        if (sig->maximum)
-            free(sig->maximum);
-        if (sig->extra)
-            table_free(sig->extra, 1);
-        list_remove_item(sig, (void**)&db->registered_inputs);
+        mapper_db_remove_signal(db, sig, 0);
     }
 }
 
+void mapper_db_remove_output_by_name(mapper_db db, const char *device_name,
+                                     const char *signal_name)
+{
+    mapper_db_signal sig;
+    sig = mapper_db_get_output_by_device_and_signal_names(db, device_name,
+                                                          signal_name);
+
+    if (!sig)
+        return;
+
+    mapper_db_remove_connections_by_query(db,
+        mapper_db_get_connections_by_src_device_and_signal_names(db,
+                                                                 device_name,
+                                                                 signal_name));
+
+    mapper_db_remove_signal(db, sig, 1);
+}
+
 void mapper_db_remove_outputs_by_query(mapper_db db,
                                        mapper_db_signal_t **s)
 {
@@ -1437,26 +1536,7 @@ void mapper_db_remove_outputs_by_query(mapper_db db,
         mapper_db_signal sig = *s;
         s = mapper_db_signal_next(s);
 
-        fptr_list cb = db->signal_callbacks;
-        while (cb) {
-            mapper_db_signal_handler *h = cb->f;
-            h(sig, MDB_REMOVE, cb->context);
-            cb = cb->next;
-        }
-
-        if (sig->name)
-            free(sig->name);
-        if (sig->device_name)
-            free(sig->device_name);
-        if (sig->unit)
-            free(sig->unit);
-        if (sig->minimum)
-            free(sig->minimum);
-        if (sig->maximum)
-            free(sig->maximum);
-        if (sig->extra)
-            table_free(sig->extra, 1);
-        list_remove_item(sig, (void**)&db->registered_outputs);
+        mapper_db_remove_signal(db, sig, 1);
     }
 }
 
@@ -1475,6 +1555,10 @@ static int update_connection_record_params(mapper_db_connection con,
 
     updated += update_string_if_different(&con->src_name, src_name);
     updated += update_string_if_different(&con->dest_name, dest_name);
+
+    if (!params)
+        return updated;
+
     updated += update_char_if_arg(&con->src_type, params, AT_SRC_TYPE);
     updated += update_char_if_arg(&con->dest_type, params, AT_DEST_TYPE);
     updated += update_int_if_arg(&con->src_length, params, AT_SRC_LENGTH);
@@ -1626,7 +1710,7 @@ int mapper_db_add_or_update_connection_params(mapper_db db,
                                               mapper_message_t *params)
 {
     mapper_db_connection con;
-    int rc = 0, updated = 0;
+    int rc = 0, updated = 0, i;
 
     con = mapper_db_get_connection_by_signal_full_names(db, src_name,
                                                         dest_name);
@@ -1638,6 +1722,17 @@ int mapper_db_add_or_update_connection_params(mapper_db db,
         con->src_max = 0;
         con->extra = table_new();
         rc = 1;
+
+        // also add devices if necessary
+        char devname[128]= "/";
+        for (i = 1; i < 127 && src_name[i] != '/' ; i++)
+            devname[i] = src_name[i];
+        devname[i] = '\0';
+        mapper_db_add_or_update_device_params(db, devname, 0, 0);
+        for (i = 1; i < 127 && dest_name[i] != '/' ; i++)
+            devname[i] = dest_name[i];
+        devname[i] = '\0';
+        mapper_db_add_or_update_device_params(db, devname, 0, 0);
     }
 
     if (con) {
@@ -2170,6 +2265,10 @@ static int update_link_record_params(mapper_db_link link,
     int i, j, num_scopes = 0, updated = 0;
     updated += update_string_if_different(&link->src_name, src_name);
     updated += update_string_if_different(&link->dest_name, dest_name);
+
+    if (!params)
+        return updated;
+
     updated += update_int_if_arg(&link->src_port, params, AT_SRC_PORT);
     updated += update_int_if_arg(&link->dest_port, params, AT_DEST_PORT);
 
@@ -2194,11 +2293,6 @@ static int update_link_record_params(mapper_db_link link,
     for (i=0; i<num_scopes; i++)
         updated += (1 - mapper_db_link_add_scope(link, &a_scopes[i]->s));
 
-    if (num_scopes != link->num_scopes) {
-        link->num_scopes = num_scopes;
-        updated++;
-    }
-
     updated += mapper_msg_add_or_update_extra_params(link->extra, params);
     return updated;
 }
@@ -2217,6 +2311,10 @@ int mapper_db_add_or_update_link_params(mapper_db db,
         link = (mapper_db_link) list_new_item(sizeof(mapper_db_link_t));
         link->extra = table_new();
         rc = 1;
+
+        // also add devices if neccesary
+        mapper_db_add_or_update_device_params(db, src_name, 0, 0);
+        mapper_db_add_or_update_device_params(db, dest_name, 0, 0);
     }
 
     if (link) {
diff --git a/src/device.c b/src/device.c
index 6df6c82..ab25267 100644
--- a/src/device.c
+++ b/src/device.c
@@ -66,6 +66,9 @@ mapper_device mdev_new(const char *name_prefix, int port,
     md->props.extra = table_new();
     md->flags = 0;
 
+//    md->link_timeout_sec = ADMIN_TIMEOUT_SEC;
+    md->link_timeout_sec = 0;
+
     mapper_admin_add_device(md->admin, md);
 
     return md;
@@ -80,14 +83,15 @@ void mdev_free(mapper_device md)
 
     if (md->registered) {
         // A registered device must tell the network it is leaving.
-        mapper_admin_send(md->admin, ADM_LOGOUT, 0, "s", mdev_name(md));
+        mapper_admin_set_bundle_dest_bus(md->admin);
+        mapper_admin_bundle_message(md->admin, ADM_LOGOUT, 0, "s", mdev_name(md));
     }
 
     // First release active instances
     mapper_signal sig;
     if (md->outputs) {
         // release all active output instances
-        for (i = 0; i < md->props.n_outputs; i++) {
+        for (i = 0; i < md->props.num_outputs; i++) {
             sig = md->outputs[i];
             for (j = 0; j < sig->id_map_length; j++) {
                 if (sig->id_maps[j].instance) {
@@ -98,7 +102,7 @@ void mdev_free(mapper_device md)
     }
     if (md->inputs) {
         // release all active input instances
-        for (i = 0; i < md->props.n_inputs; i++) {
+        for (i = 0; i < md->props.num_inputs; i++) {
             sig = md->inputs[i];
             for (j = 0; j < sig->id_map_length; j++) {
                 if (sig->id_maps[j].instance) {
@@ -115,12 +119,12 @@ void mdev_free(mapper_device md)
         mdev_remove_receiver(md, md->receivers);
 
     if (md->outputs) {
-        for (i = 0; i < md->props.n_outputs; i++)
+        for (i = 0; i < md->props.num_outputs; i++)
             msig_free(md->outputs[i]);
         free(md->outputs);
     }
     if (md->inputs) {
-        for (i = 0; i < md->props.n_inputs; i++) {
+        for (i = 0; i < md->props.num_inputs; i++) {
             msig_free(md->inputs[i]);
         }
         free(md->inputs);
@@ -164,7 +168,7 @@ void mdev_registered(mapper_device md)
     md->registered = 1;
     /* Add device name to signals. Also add device name hash to
      * locally-activated signal instances. */
-    for (i = 0; i < md->props.n_inputs; i++) {
+    for (i = 0; i < md->props.num_inputs; i++) {
         md->inputs[i]->props.device_name = (char *)mdev_name(md);
         for (j = 0; j < md->inputs[i]->id_map_length; j++) {
             if (md->inputs[i]->id_maps[j].map &&
@@ -172,7 +176,7 @@ void mdev_registered(mapper_device md)
                 md->inputs[i]->id_maps[j].map->origin = md->props.name_hash;
         }
     }
-    for (i = 0; i < md->props.n_outputs; i++) {
+    for (i = 0; i < md->props.num_outputs; i++) {
         md->outputs[i]->props.device_name = (char *)mdev_name(md);
         for (j = 0; j < md->outputs[i]->id_map_length; j++) {
             if (md->outputs[i]->id_maps[j].map &&
@@ -203,173 +207,186 @@ static void mdev_increment_version(mapper_device md)
     }
 }
 
+/* This handler needs to be able to handle a number of overlapping cases:
+ *     - scalar and vector signal values
+ *     - null vector elements
+ *     - entirely null typestrings (used to release instances)
+ *     - multiple signal samples in one message ("count" > 1)
+ */
 static int handler_signal(const char *path, const char *types,
                           lo_arg **argv, int argc, lo_message msg,
                           void *user_data)
 {
     mapper_signal sig = (mapper_signal) user_data;
-    mapper_device md = sig->device;
-    void *dataptr = 0;
-    int count = 1;
+    mapper_device md;
+    int i = 0, j, k, count = 1, nulls = 0;
+    int index = 0, is_instance_update = 0, origin, public_id;
+    mapper_id_map map;
 
-    if (!md) {
-        trace("error, sig->device==0\n");
+    if (!sig || !sig->handler || !(md = sig->device)) {
+        trace("error, cannot retrieve user_data\n");
         return 0;
     }
 
-    if (!sig)
+    if (!argc)
         return 0;
 
-    lo_timetag tt = lo_message_get_timestamp(msg);
-
-    int index = 0;
-    if (!sig->id_maps[0].instance)
-        index = msig_get_instance_with_local_id(sig, 0, 1, &tt);
-    if (index < 0)
+    // We need to consider that there may be properties appended to the msg
+    // check length and types
+    int value_len = 0;
+    while (value_len < argc && types[value_len] != 's' && types[value_len] != 'S') {
+        if (types[i] == 'N')
+            nulls++;
+        else if (types[i] != sig->props.type) {
+            trace("error: unexpected typestring for signal %s\n",
+                  sig->props.name);
+            return 0;
+        }
+        value_len++;
+    }
+    if (value_len < sig->props.length || value_len % sig->props.length != 0) {
+        trace("error: unexpected length for signal %s\n",
+              sig->props.name);
         return 0;
+    }
+    count = value_len / sig->props.length;
 
-    mapper_signal_instance si = sig->id_maps[index].instance;
+    if (argc >= value_len + 3) {
+        // could be update of specific instance
+        if (types[value_len] != 's' && types[value_len] != 'S')
+            return 0;
+        if (strcmp(&argv[value_len]->s, "@instance") != 0)
+            return 0;
+        if (types[value_len+1] != 'i' || types[value_len+2] != 'i')
+            return 0;
+        is_instance_update = 1;
+        origin = argv[value_len+1]->i32;
+        public_id = argv[value_len+2]->i32;
+    }
 
     // TODO: optionally discard out-of-order messages
     // requires timebase sync for many-to-one connections or local updates
     //    if (sig->discard_out_of_order && out_of_order(si->timetag, tt))
     //        return 0;
-
-    if (types[0] == LO_BLOB) {
-        dataptr = lo_blob_dataptr((lo_blob)argv[0]);
-        count = lo_blob_datasize((lo_blob)argv[0]) /
-            mapper_type_size(sig->props.type);
-    }
-    else
-        dataptr = argv[0];
-
-    if (types[0] == LO_NIL) {
-        si->has_value = 0;
-    }
-    else {
-        /* This is cheating a bit since we know that the arguments pointed
-         * to by argv are layed out sequentially in memory.  It's not
-         * clear if liblo's semantics guarantee it, but known to be true
-         * on all platforms. */
-        // TODO: should copy last value from sample vector (or add history)
-        memcpy(si->value, dataptr, msig_vector_bytes(sig));
-        si->has_value = 1;
-    }
-    si->timetag.sec = tt.sec;
-    si->timetag.frac = tt.frac;
-    if (sig->handler)
-        sig->handler(sig, &sig->props, sig->id_maps[index].map->local,
-                     dataptr, count, &tt);
-    if (!sig->props.is_output)
-        mdev_receive_update(md, sig, index, tt);
-
-    return 0;
-}
-
-static int handler_signal_instance(const char *path, const char *types,
-                                   lo_arg **argv, int argc, lo_message msg,
-                                   void *user_data)
-{
-    mapper_signal sig = (mapper_signal) user_data;
-    mapper_device md = sig->device;
-    void *dataptr = 0;
-    int count = 1;
-
-    if (!md) {
-        trace("error, sig->device==0\n");
-        return 0;
-    }
-    if (argc < 3)
-        return 0;
-
-    int origin = argv[0]->i32;
-    int public_id = argv[1]->i32;
-
-    int index = msig_find_instance_with_remote_ids(sig, origin, public_id,
-                                                   IN_RELEASED_LOCALLY);
-
     lo_timetag tt = lo_message_get_timestamp(msg);
 
-    mapper_id_map map;
-    if (index < 0) {    // no instance found with this map
-        // Don't activate instance just to release it again
-        if (types[2] == LO_NIL || types[2] == LO_FALSE)
-            return 0;
-
-        // otherwise try to init reserved/stolen instance with device map
-        index = msig_get_instance_with_remote_ids(sig, origin, public_id, 0, &tt);
-        if (index < 0) {
-            trace("no instances available for origin=%ld, public_id=%ld\n",
-                  (long)origin, (long)public_id);
-            return 0;
+    if (is_instance_update) {
+        index = msig_find_instance_with_remote_ids(sig, origin, public_id,
+                                                   IN_RELEASED_REMOTELY);
+        if (index < 0) {    // no instance found with this map
+            // Don't activate instance just to release it again
+            if (count == 1 && nulls == sig->props.length)
+                return 0;
+            
+            // otherwise try to init reserved/stolen instance with device map
+            index = msig_get_instance_with_remote_ids(sig, origin, public_id, 0, &tt);
+            if (index < 0) {
+                trace("no local instances available for remote instance %ld:%ld\n",
+                      (long)origin, (long)public_id);
+                return 0;
+            }
         }
-    }
-    else {
-        if (sig->id_maps[index].status & IN_RELEASED_LOCALLY) {
-            // map was already released locally, we are only interested in release messages
-            if (types[2] == LO_NIL || types[2] == LO_FALSE) {
-                // we can clear signal's reference to map
-                map = sig->id_maps[index].map;
-                sig->id_maps[index].map = 0;
-                map->refcount_remote--;
-                if (map->refcount_remote <= 0 && map->refcount_local <= 0) {
-                    mdev_remove_instance_id_map(md, map);
+        else {
+            if (sig->id_maps[index].status & IN_RELEASED_LOCALLY) {
+                // map was already released locally, we are only interested in release messages
+                if (count == 1 && nulls == sig->props.length) {
+                    // we can clear signal's reference to map
+                    map = sig->id_maps[index].map;
+                    sig->id_maps[index].map = 0;
+                    map->refcount_remote--;
+                    if (map->refcount_remote <= 0 && map->refcount_local <= 0) {
+                        mdev_remove_instance_id_map(md, map);
+                    }
                 }
+                return 0;
+            }
+            else if (!sig->id_maps[index].instance) {
+                trace("error: missing instance!\n");
+                return 0;
             }
-            return 0;
         }
-        else if (!sig->id_maps[index].instance) {
-            trace("error: missing instance!\n");
+    }
+    else {
+        index = 0;
+        if (!sig->id_maps[0].instance)
+            index = msig_get_instance_with_local_id(sig, 0, 1, &tt);
+        if (index < 0)
             return 0;
-        }
     }
-
     mapper_signal_instance si = sig->id_maps[index].instance;
     map = sig->id_maps[index].map;
 
-    // TODO: optionally discard out-of-order messages
-    // requires timebase sync for many-to-one connections or local updates
-    //    if (sig->discard_out_of_order && out_of_order(si->timetag, tt))
-    //        return 0;
-
+    // TODO: alter timetags for multicount updates with missing handler calls?
     si->timetag.sec = tt.sec;
     si->timetag.frac = tt.frac;
 
-    if (types[2] == LO_NIL) {
-        sig->id_maps[index].status |= IN_RELEASED_REMOTELY;
-        map->refcount_remote--;
-        if (sig->instance_event_handler
-            && (sig->instance_event_flags & IN_UPSTREAM_RELEASE)) {
-            sig->instance_event_handler(sig, &sig->props, map->local,
-                                        IN_UPSTREAM_RELEASE, &tt);
-        }
-        else if (sig->handler) {
-            sig->handler(sig, &sig->props, map->local, dataptr, count, &tt);
+    int size = mapper_type_size(sig->props.type);
+    void *out_buffer = count == 1 ? 0 : alloca(count * sig->props.length * size);
+    int vals, out_count = 0;
+
+    /* As currently implemented, instance release messages cannot be embedded in
+     * multi-count messages. */
+    for (i = 0, k = 0; i < count; i++) {
+        // check if each update will result in a handler call
+        // all nulls -> release instance, sig has no value, break "count"
+        vals = 0;
+        for (j = 0; j < sig->props.length; j++, k++) {
+            if (types[k] == 'N')
+                continue;
+            memcpy(si->value + j * size, argv[k], size);
+            si->has_value_flags[j / 8] |= 1 << (j % 8);
+            vals++;
         }
-    }
-    else {
-        if (types[2] == LO_BLOB) {
-            dataptr = lo_blob_dataptr((lo_blob)argv[2]);
-            count = lo_blob_datasize((lo_blob)argv[2]) /
-                mapper_type_size(sig->props.type);
+
+        if (vals == 0) {
+            // protocol: update with all nulls sets signal has_value to 0
+            if (si->has_value) {
+                si->has_value = 0;
+                memset(si->has_value_flags, 0, sig->props.length / 8 + 1);
+                if (is_instance_update) {
+                    // TODO: handle multiple upstream devices
+                    sig->id_maps[index].status |= IN_RELEASED_REMOTELY;
+                    map->refcount_remote--;
+                    if (sig->instance_event_handler
+                        && (sig->instance_event_flags & IN_UPSTREAM_RELEASE)) {
+                        sig->instance_event_handler(sig, &sig->props, map->local,
+                                                    IN_UPSTREAM_RELEASE, &tt);
+                    }
+                    else if (sig->handler)
+                        sig->handler(sig, &sig->props, map->local, 0, 1, &tt);
+                }
+                else {
+                    // call handler with null value
+                    sig->handler(sig, &sig->props, map->local, 0, 1, &tt);
+                }
+            }
+            return 0;
         }
-        else {
-            /* This is cheating a bit since we know that the arguments pointed
-             * to by argv are layed out sequentially in memory.  It's not
-             * clear if liblo's semantics guarantee it, but known to be true
-             * on all platforms. */
-            // TODO: should copy last value from sample vector (or add history)
-            memcpy(si->value, argv[2], msig_vector_bytes(sig));
+
+        if (si->has_value || memcmp(si->has_value_flags, sig->has_complete_value,
+                                    sig->props.length / 8 + 1)==0) {
+            if (count > 1) {
+                memcpy(out_buffer + out_count * sig->props.length * size,
+                       si->value, size);
+                out_count++;
+            }
+            else
+                sig->handler(sig, &sig->props, map->local, si->value, 1, &tt);
             si->has_value = 1;
-            dataptr = si->value;
         }
-
-        if (sig->handler) {
-            sig->handler(sig, &sig->props, map->local, dataptr, count, &tt);
+        else {
+            // Call handler with NULL value
+            sig->handler(sig, &sig->props, map->local, 0, 0, &tt);
         }
     }
-    if (!sig->props.is_output)
+    if (out_count) {
+        sig->handler(sig, &sig->props, map->local, out_buffer, out_count, &tt);
+    }
+    // TODO: handle cases where count > 1
+    if (!sig->props.is_output) {
         mdev_receive_update(md, sig, index, tt);
+    }
+
     return 0;
 }
 
@@ -407,6 +424,8 @@ static int handler_query(const char *path, const char *types,
 {
     mapper_signal sig = (mapper_signal) user_data;
     mapper_device md = sig->device;
+    int length = sig->props.length;
+    char type = sig->props.type;
 
     if (!md) {
         trace("error, sig->device==0\n");
@@ -419,139 +438,175 @@ static int handler_query(const char *path, const char *types,
         return 0;
 
     int i, j, sent = 0;
-    lo_message m = lo_message_new();
-    if (!m)
-        return 0;
+
+    // respond with same timestamp as query
+    // TODO: should we also include actual timestamp for signal value?
     lo_timetag tt = lo_message_get_timestamp(msg);
     lo_bundle b = lo_bundle_new(tt);
 
+    // query response string is first argument
+    const char *response_path = &argv[0]->s;
+
+    // vector length and data type may also be provided
+    if (argc >= 3) {
+        if (types[1] == 'i')
+            length = argv[1]->i32;
+        if (types[2] == 'c')
+            type = argv[2]->c;
+    }
+
     mapper_signal_instance si;
     for (i = 0; i < sig->id_map_length; i++) {
         if (!(si = sig->id_maps[i].instance))
             continue;
+        lo_message m = lo_message_new();
+        if (!m)
+            continue;
+        message_add_coerced_signal_instance_value(m, sig, si, length, type);
         if (sig->props.num_instances > 1) {
+            lo_message_add_string(m, "@instance");
             lo_message_add_int32(m, (long)sig->id_maps[i].map->origin);
             lo_message_add_int32(m, (long)sig->id_maps[i].map->public);
         }
-        if (si->has_value) {
-            if (sig->props.type == 'f') {
-                float *v = si->value;
-                for (j = 0; j < sig->props.length; j++)
-                    lo_message_add_float(m, v[j]);
-            }
-            else if (sig->props.type == 'i') {
-                int *v = si->value;
-                for (j = 0; j < sig->props.length; j++)
-                    lo_message_add_int32(m, v[j]);
-            }
-            else if (sig->props.type == 'd') {
-                double *v = si->value;
-                for (j = 0; j < sig->props.length; j++)
-                    lo_message_add_double(m, v[j]);
-            }
-        }
-        else {
-            lo_message_add_nil(m);
-        }
-        lo_bundle_add_message(b, &argv[0]->s, m);
+        lo_bundle_add_message(b, response_path, m);
         sent++;
     }
     if (!sent) {
-        // If there are no active instances, send null response
-        lo_message_add_nil(m);
-        lo_bundle_add_message(b, &argv[0]->s, m);
+        // If there are no active instances, send a single null response
+        lo_message m = lo_message_new();
+        if (m) {
+            for (j = 0; j < length; j++)
+                lo_message_add_nil(m);
+            lo_bundle_add_message(b, response_path, m);
+        }
     }
+    // TODO: do we need to free the lo_address here?
     lo_send_bundle(lo_message_get_source(msg), b);
     lo_bundle_free_messages(b);
     return 0;
 }
 
-
-// Add an input signal to a mapper device.
-mapper_signal mdev_add_input(mapper_device md, const char *name, int length,
-                             char type, const char *unit,
-                             void *minimum, void *maximum,
-                             mapper_signal_update_handler *handler,
-                             void *user_data)
+static mapper_signal add_input_internal(mapper_device md, const char *name,
+                                        int length, char type, const char *unit,
+                                        void *minimum, void *maximum,
+                                        int num_instances,
+                                        mapper_signal_update_handler *handler,
+                                        void *user_data)
 {
     if (mdev_get_input_by_name(md, name, 0))
         return 0;
-    char *type_string = 0, *signal_get = 0;
+    char *signal_get = 0;
     mapper_signal sig = msig_new(name, length, type, 0, unit, minimum,
-                                 maximum, handler, user_data);
+                                 maximum, num_instances, handler, user_data);
     if (!sig)
         return 0;
-    md->props.n_inputs++;
-    grow_ptr_array((void **) &md->inputs, md->props.n_inputs,
+    md->props.num_inputs++;
+    grow_ptr_array((void **) &md->inputs, md->props.num_inputs,
                    &md->n_alloc_inputs);
 
     mdev_increment_version(md);
 
-    md->inputs[md->props.n_inputs - 1] = sig;
+    md->inputs[md->props.num_inputs - 1] = sig;
     sig->device = md;
 
-    type_string = (char*) realloc(type_string, sig->props.length + 3);
-    type_string[0] = type_string[1] = 'i';
-    memset(type_string + 2, sig->props.type, sig->props.length);
-    type_string[sig->props.length + 2] = 0;
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         type_string + 2,
-                         handler_signal, (void *) (sig));
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         "b",
-                         handler_signal, (void *) (sig));
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         "N",
-                         handler_signal, (void *) (sig));
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         type_string,
-                         handler_signal_instance, (void *) (sig));
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         "iib",
-                         handler_signal_instance, (void *) (sig));
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         "iiN",
-                         handler_signal_instance, (void *) (sig));
+    lo_server_add_method(md->server, sig->props.name, NULL, handler_signal,
+                         (void *) (sig));
+
     int len = strlen(sig->props.name) + 5;
     signal_get = (char*) realloc(signal_get, len);
     snprintf(signal_get, len, "%s%s", sig->props.name, "/get");
-    lo_server_add_method(md->server,
-                         signal_get,
-                         "s",
+    lo_server_add_method(md->server, signal_get, NULL,
                          handler_query, (void *) (sig));
-    free(type_string);
+
     free(signal_get);
 
+    if (md->registered) {
+        sig->props.device_name = (char *)mdev_name(md);
+        // Notify subscribers
+        mapper_admin_set_bundle_dest_subscribers(md->admin, SUB_DEVICE_INPUTS);
+        mapper_admin_send_signal(md->admin, md, sig);
+    }
+
     return sig;
 }
 
+// Add an input signal to a mapper device.
+mapper_signal mdev_add_input(mapper_device md, const char *name, int length,
+                             char type, const char *unit,
+                             void *minimum, void *maximum,
+                             mapper_signal_update_handler *handler,
+                             void *user_data)
+{
+    return add_input_internal(md, name, length, type, unit, minimum, maximum,
+                              -1, handler, user_data);
+}
+
+// Add an input signal with multiple instances to a mapper device.
+mapper_signal mdev_add_poly_input(mapper_device md, const char *name, int length,
+                                  char type, const char *unit,
+                                  void *minimum, void *maximum,
+                                  int num_instances,
+                                  mapper_signal_update_handler *handler,
+                                  void *user_data)
+{
+    if (num_instances < 0)
+        num_instances = 0;
+    return add_input_internal(md, name, length, type, unit, minimum, maximum,
+                              num_instances, handler, user_data);
+}
+
 // Add an output signal to a mapper device.
-mapper_signal mdev_add_output(mapper_device md, const char *name, int length,
-                              char type, const char *unit, void *minimum, void *maximum)
+static mapper_signal add_output_internal(mapper_device md, const char *name,
+                                         int length, char type, const char *unit,
+                                         void *minimum, void *maximum,
+                                         int num_instances)
 {
     if (mdev_get_output_by_name(md, name, 0))
         return 0;
     mapper_signal sig = msig_new(name, length, type, 1, unit, minimum,
-                                 maximum, 0, 0);
+                                 maximum, num_instances, 0, 0);
     if (!sig)
         return 0;
-    md->props.n_outputs++;
-    grow_ptr_array((void **) &md->outputs, md->props.n_outputs,
+    md->props.num_outputs++;
+    grow_ptr_array((void **) &md->outputs, md->props.num_outputs,
                    &md->n_alloc_outputs);
 
     mdev_increment_version(md);
 
-    md->outputs[md->props.n_outputs - 1] = sig;
+    md->outputs[md->props.num_outputs - 1] = sig;
     sig->device = md;
+
+    if (md->registered) {
+        sig->props.device_name = (char *)mdev_name(md);
+        // Notify subscribers
+        mapper_admin_set_bundle_dest_subscribers(md->admin, SUB_DEVICE_OUTPUTS);
+        mapper_admin_send_signal(md->admin, md, sig);
+    }
+
     return sig;
 }
 
+// Add an output signal to a mapper device.
+mapper_signal mdev_add_output(mapper_device md, const char *name,
+                              int length, char type, const char *unit,
+                              void *minimum, void *maximum)
+{
+    return add_output_internal(md, name, length, type, unit,
+                               minimum, maximum, -1);
+}
+
+// Add an output signal with multiple instances to a mapper device.
+mapper_signal mdev_add_poly_output(mapper_device md, const char *name,
+                                   int length, char type, const char *unit,
+                                   void *minimum, void *maximum,
+                                   int num_instances)
+{
+    if (num_instances < 0)
+        num_instances = 0;
+    return add_output_internal(md, name, length, type, unit,
+                               minimum, maximum, num_instances);
+}
+
 void mdev_add_signal_methods(mapper_device md, mapper_signal sig)
 {
     // TODO: handle adding and removing input signal methods also?
@@ -567,22 +622,8 @@ void mdev_add_signal_methods(mapper_device md, mapper_signal sig)
     type[0] = type[1] = 'i';
     memset(type + 2, sig->props.type, sig->props.length);
     type[sig->props.length + 2] = 0;
-    lo_server_add_method(md->server,
-                         path,
-                         type + 2,
-                         handler_signal, (void *)sig);
-    lo_server_add_method(md->server,
-                         path,
-                         type,
-                         handler_signal_instance, (void *)sig);
-    lo_server_add_method(md->server,
-                         path,
-                         "N",
+    lo_server_add_method(md->server, path, NULL,
                          handler_signal, (void *)sig);
-    lo_server_add_method(md->server,
-                         path,
-                         "iiN",
-                         handler_signal_instance, (void *)sig);
     md->n_output_callbacks ++;
     free(path);
     free(type);
@@ -590,28 +631,21 @@ void mdev_add_signal_methods(mapper_device md, mapper_signal sig)
 
 void mdev_remove_signal_methods(mapper_device md, mapper_signal sig)
 {
-    char *type = 0, *path = 0;
+    char *path = 0;
     int len, i;
     if (!md || !sig)
         return;
-    for (i=0; i<md->props.n_outputs; i++) {
+    for (i=0; i<md->props.num_outputs; i++) {
         if (md->outputs[i] == sig)
             break;
     }
-    if (i==md->props.n_outputs)
+    if (i==md->props.num_outputs)
         return;
-    type = (char*) realloc(type, sig->props.length + 3);
-    type[0] = type[1] = 'i';
-    memset(type + 2, sig->props.type,
-           sig->props.length);
-    type[sig->props.length + 2] = 0;
+
     len = (int) strlen(sig->props.name) + 5;
     path = (char*) realloc(path, len);
     snprintf(path, len, "%s%s", sig->props.name, "/got");
-    lo_server_del_method(md->server, path, type);
-    lo_server_del_method(md->server, path, type + 2);
-    lo_server_del_method(md->server, path, "N");
-    lo_server_del_method(md->server, path, "iiN");
+    lo_server_del_method(md->server, path, NULL);
     md->n_output_callbacks --;
 }
 
@@ -620,9 +654,8 @@ void mdev_add_instance_release_request_callback(mapper_device md, mapper_signal
     if (!sig->props.is_output)
         return;
 
-    lo_server_add_method(md->server,
-                         sig->props.name,
-                         "iiF",
+    // TODO: use normal release message?
+    lo_server_add_method(md->server, sig->props.name, "iiF",
                          handler_instance_release_request, (void *) (sig));
     md->n_output_callbacks ++;
 }
@@ -632,11 +665,11 @@ void mdev_remove_instance_release_request_callback(mapper_device md, mapper_sign
     int i;
     if (!md || !sig)
         return;
-    for (i=0; i<md->props.n_outputs; i++) {
+    for (i=0; i<md->props.num_outputs; i++) {
         if (md->outputs[i] == sig)
             break;
     }
-    if (i==md->props.n_outputs)
+    if (i==md->props.num_outputs)
         return;
     lo_server_del_method(md->server, sig->props.name, "iiF");
     md->n_output_callbacks --;
@@ -646,26 +679,18 @@ void mdev_remove_input(mapper_device md, mapper_signal sig)
 {
     int i, n;
     char str1[1024], str2[1024];
-    for (i=0; i<md->props.n_inputs; i++) {
+    for (i=0; i<md->props.num_inputs; i++) {
         if (md->inputs[i] == sig)
             break;
     }
-    if (i==md->props.n_inputs)
+    if (i==md->props.num_inputs)
         return;
 
-    for (n=i; n<(md->props.n_inputs-1); n++) {
+    for (n=i; n<(md->props.num_inputs-1); n++) {
         md->inputs[n] = md->inputs[n+1];
     }
 
-    str1[0] = str1[1] = 'i';
-    memset(str1 + 2, sig->props.type, sig->props.length);
-    str1[sig->props.length + 2] = 0;
-    lo_server_del_method(md->server, sig->props.name, str1);
-    lo_server_del_method(md->server, sig->props.name, str1 + 2);
-    lo_server_del_method(md->server, sig->props.name, "b");
-    lo_server_del_method(md->server, sig->props.name, "N");
-    lo_server_del_method(md->server, sig->props.name, "iib");
-    lo_server_del_method(md->server, sig->props.name, "iiN");
+    lo_server_del_method(md->server, sig->props.name, NULL);
 
     snprintf(str1, 1024, "%s/get", sig->props.name);
     lo_server_del_method(md->server, str1, NULL);
@@ -677,12 +702,14 @@ void mdev_remove_input(mapper_device md, mapper_signal sig)
         while (rs) {
             if (rs->signal == sig) {
                 // need to disconnect?
+                mapper_admin_set_bundle_dest_mesh(md->admin, r->admin_addr);
                 mapper_connection c = rs->connections;
                 while (c) {
                     snprintf(str1, 1024, "%s%s", r->props.src_name,
                              c->props.src_name);
-                    mapper_admin_send(md->admin, ADM_DISCONNECT, 0, "ss",
-                                      str1, str2);
+                    // TODO: send directly to source device?
+                    mapper_admin_bundle_message(md->admin, ADM_DISCONNECT, 0,
+                                                "ss", str1, str2);
                     mapper_connection temp = c->next;
                     mapper_receiver_remove_connection(r, c);
                     c = temp;
@@ -694,7 +721,13 @@ void mdev_remove_input(mapper_device md, mapper_signal sig)
         r = r->next;
     }
 
-    md->props.n_inputs --;
+    if (md->registered) {
+        // Notify subscribers
+        mapper_admin_set_bundle_dest_subscribers(md->admin, SUB_DEVICE_INPUTS);
+        mapper_admin_send_signal_removed(md->admin, md, sig);
+    }
+
+    md->props.num_inputs --;
     mdev_increment_version(md);
     msig_free(sig);
 }
@@ -703,14 +736,14 @@ void mdev_remove_output(mapper_device md, mapper_signal sig)
 {
     int i, n;
     char str1[1024], str2[1024];
-    for (i=0; i<md->props.n_outputs; i++) {
+    for (i=0; i<md->props.num_outputs; i++) {
         if (md->outputs[i] == sig)
             break;
     }
-    if (i==md->props.n_outputs)
+    if (i==md->props.num_outputs)
         return;
 
-    for (n=i; n<(md->props.n_outputs-1); n++) {
+    for (n=i; n<(md->props.num_outputs-1); n++) {
         md->outputs[n] = md->outputs[n+1];
     }
     if (sig->handler) {
@@ -729,12 +762,14 @@ void mdev_remove_output(mapper_device md, mapper_signal sig)
         while (rs) {
             if (rs->signal == sig) {
                 // need to disconnect?
+                mapper_admin_set_bundle_dest_mesh(md->admin, r->admin_addr);
                 mapper_connection c = rs->connections;
                 while (c) {
                     snprintf(str2, 1024, "%s%s", r->props.dest_name,
                              c->props.dest_name);
-                    mapper_admin_send(md->admin, ADM_DISCONNECTED, 0, "ss",
-                                      str1, str2);
+                    // TODO: send directly to source device?
+                    mapper_admin_bundle_message(md->admin, ADM_DISCONNECTED, 0,
+                                                "ss", str1, str2);
                     mapper_connection temp = c->next;
                     mapper_router_remove_connection(r, c);
                     c = temp;
@@ -746,29 +781,35 @@ void mdev_remove_output(mapper_device md, mapper_signal sig)
         r = r->next;
     }
 
-    md->props.n_outputs --;
+    if (md->registered) {
+        // Notify subscribers
+        mapper_admin_set_bundle_dest_subscribers(md->admin, SUB_DEVICE_OUTPUTS);
+        mapper_admin_send_signal_removed(md->admin, md, sig);
+    }
+
+    md->props.num_outputs --;
     mdev_increment_version(md);
     msig_free(sig);
 }
 
 int mdev_num_inputs(mapper_device md)
 {
-    return md->props.n_inputs;
+    return md->props.num_inputs;
 }
 
 int mdev_num_outputs(mapper_device md)
 {
-    return md->props.n_outputs;
+    return md->props.num_outputs;
 }
 
 int mdev_num_links_in(mapper_device md)
 {
-    return md->props.n_links_in;
+    return md->props.num_links_in;
 }
 
 int mdev_num_links_out(mapper_device md)
 {
-    return md->props.n_links_out;
+    return md->props.num_links_out;
 }
 
 int mdev_num_connections_in(mapper_device md)
@@ -776,7 +817,7 @@ int mdev_num_connections_in(mapper_device md)
     mapper_receiver r = md->receivers;
     int count = 0;
     while (r) {
-        count += r->n_connections;
+        count += r->num_connections;
         r = r->next;
     }
     return count;
@@ -787,7 +828,7 @@ int mdev_num_connections_out(mapper_device md)
     mapper_router r = md->routers;
     int count = 0;
     while (r) {
-        count += r->n_connections;
+        count += r->num_connections;
         r = r->next;
     }
     return count;
@@ -811,7 +852,7 @@ mapper_signal mdev_get_input_by_name(mapper_device md, const char *name,
         return 0;
 
     slash = name[0]=='/' ? 1 : 0;
-    for (i=0; i<md->props.n_inputs; i++)
+    for (i=0; i<md->props.num_inputs; i++)
     {
         if (strcmp(md->inputs[i]->props.name + 1,
                    name + slash)==0)
@@ -832,7 +873,7 @@ mapper_signal mdev_get_output_by_name(mapper_device md, const char *name,
         return 0;
 
     slash = name[0]=='/' ? 1 : 0;
-    for (i=0; i<md->props.n_outputs; i++)
+    for (i=0; i<md->props.num_outputs; i++)
     {
         if (strcmp(md->outputs[i]->props.name + 1,
                    name + slash)==0)
@@ -847,14 +888,14 @@ mapper_signal mdev_get_output_by_name(mapper_device md, const char *name,
 
 mapper_signal mdev_get_input_by_index(mapper_device md, int index)
 {
-    if (index >= 0 && index < md->props.n_inputs)
+    if (index >= 0 && index < md->props.num_inputs)
         return md->inputs[index];
     return 0;
 }
 
 mapper_signal mdev_get_output_by_index(mapper_device md, int index)
 {
-    if (index >= 0 && index < md->props.n_outputs)
+    if (index >= 0 && index < md->props.num_outputs)
         return md->outputs[index];
     return 0;
 }
@@ -887,7 +928,7 @@ int mdev_poll(mapper_device md, int block_ms)
          * no point.  Perhaps if this is supported in the future it
          * can be a heuristic based on a recent number of messages per
          * channel per poll. */
-        while (count < (md->props.n_inputs + md->n_output_callbacks)*1
+        while (count < (md->props.num_inputs + md->n_output_callbacks)*1
                && lo_server_recv_noblock(md->server, 0))
             count++;
     }
@@ -904,24 +945,30 @@ int mdev_poll(mapper_device md, int block_ms)
 
 int mdev_num_fds(mapper_device md)
 {
-    // One for the admin input, and one for the signal input.
-    return 2;
+    // Two for the admin inputs (bus and mesh), and one for the signal input.
+    return 3;
 }
 
 int mdev_get_fds(mapper_device md, int *fds, int num)
 {
     if (num > 0)
-        fds[0] = lo_server_get_socket_fd(md->admin->admin_server);
-    if (num > 1)
-        fds[1] = lo_server_get_socket_fd(md->server);
+        fds[0] = lo_server_get_socket_fd(md->admin->bus_server);
+    if (num > 1) {
+        fds[1] = lo_server_get_socket_fd(md->admin->mesh_server);
+        if (num > 2)
+            fds[2] = lo_server_get_socket_fd(md->server);
+        else
+            return 2;
+    }
     else
         return 1;
-    return 2;
+    return 3;
 }
 
 void mdev_service_fd(mapper_device md, int fd)
 {
-    if (fd == lo_server_get_socket_fd(md->admin->admin_server))
+    // TODO: separate fds for bus and mesh comms
+    if (fd == lo_server_get_socket_fd(md->admin->bus_server))
         mapper_admin_poll(md->admin);
     else if (md->server
              && fd == lo_server_get_socket_fd(md->server))
@@ -1029,7 +1076,7 @@ void mdev_add_router(mapper_device md, mapper_router rt)
     mapper_router *r = &md->routers;
     rt->next = *r;
     *r = rt;
-    md->props.n_links_out++;
+    md->props.num_links_out++;
 }
 
 void mdev_remove_router(mapper_device md, mapper_router rt)
@@ -1039,7 +1086,7 @@ void mdev_remove_router(mapper_device md, mapper_router rt)
         if (*r == rt) {
             *r = rt->next;
             mapper_router_free(rt);
-            md->props.n_links_out--;
+            md->props.num_links_out--;
             break;
         }
         r = &(*r)->next;
@@ -1051,7 +1098,7 @@ void mdev_add_receiver(mapper_device md, mapper_receiver rc)
     mapper_receiver *r = &md->receivers;
     rc->next = *r;
     *r = rc;
-    md->props.n_links_in++;
+    md->props.num_links_in++;
 }
 
 void mdev_remove_receiver(mapper_device md, mapper_receiver rc)
@@ -1062,7 +1109,7 @@ void mdev_remove_receiver(mapper_device md, mapper_receiver rc)
         if (*r == rc) {
             *r = rc->next;
             mapper_receiver_free(rc);
-            md->props.n_links_in--;
+            md->props.num_links_in--;
             break;
         }
         r = &(*r)->next;
@@ -1152,7 +1199,7 @@ void mdev_start_server(mapper_device md, int starting_port)
 {
     if (!md->server) {
         int i;
-        char port[16], *pport = port, *type = 0, *path = 0;
+        char port[16], *pport = port, *path = 0;
 
         if (starting_port)
             sprintf(port, "%d", starting_port);
@@ -1169,70 +1216,23 @@ void mdev_start_server(mapper_device md, int starting_port)
         md->props.port = lo_server_get_port(md->server);
         trace("bound to port %i\n", md->props.port);
 
-        for (i = 0; i < md->props.n_inputs; i++) {
-            type = (char*) realloc(type, md->inputs[i]->props.length + 3);
-            type[0] = type[1] = 'i';
-            memset(type + 2, md->inputs[i]->props.type,
-                   md->inputs[i]->props.length);
-            type[md->inputs[i]->props.length + 2] = 0;
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 type + 2,
-                                 handler_signal, (void *) (md->inputs[i]));
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 "b",
+        for (i = 0; i < md->props.num_inputs; i++) {
+            lo_server_add_method(md->server, md->inputs[i]->props.name, NULL,
                                  handler_signal, (void *) (md->inputs[i]));
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 "N",
-                                 handler_signal, (void *) (md->inputs[i]));
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 type,
-                                 handler_signal_instance, (void *) (md->inputs[i]));
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 "iib",
-                                 handler_signal_instance, (void *) (md->inputs[i]));
-            lo_server_add_method(md->server,
-                                 md->inputs[i]->props.name,
-                                 "iiN",
-                                 handler_signal_instance, (void *) (md->inputs[i]));
+
             int len = (int) strlen(md->inputs[i]->props.name) + 5;
             path = (char*) realloc(path, len);
             snprintf(path, len, "%s%s", md->inputs[i]->props.name, "/get");
-            lo_server_add_method(md->server,
-                                 path,
-                                 "s",
-                                 handler_query, (void *) (md->inputs[i]));
+            lo_server_add_method(md->server, path, NULL, handler_query,
+                                 (void *) (md->inputs[i]));
         }
-        for (i = 0; i < md->props.n_outputs; i++) {
+        for (i = 0; i < md->props.num_outputs; i++) {
             if (md->outputs[i]->handler) {
-                type = (char*) realloc(type, md->outputs[i]->props.length + 3);
-                type[0] = type[1] = 'i';
-                memset(type + 2, md->outputs[i]->props.type,
-                       md->outputs[i]->props.length);
-                type[md->outputs[i]->props.length + 2] = 0;
                 int len = (int) strlen(md->outputs[i]->props.name) + 5;
                 path = (char*) realloc(path, len);
                 snprintf(path, len, "%s%s", md->outputs[i]->props.name, "/got");
-                lo_server_add_method(md->server,
-                                     path,
-                                     type + 2,
-                                     handler_signal, (void *) (md->outputs[i]));
-                lo_server_add_method(md->server,
-                                     path,
-                                     type,
-                                     handler_signal_instance, (void *) (md->outputs[i]));
-                lo_server_add_method(md->server,
-                                     path,
-                                     "N",
-                                     handler_signal, (void *) (md->outputs[i]));
-                lo_server_add_method(md->server,
-                                     path,
-                                     "iiN",
-                                     handler_signal_instance, (void *) (md->outputs[i]));
+                lo_server_add_method(md->server, path, NULL, handler_signal,
+                                     (void *) (md->outputs[i]));
                 md->n_output_callbacks ++;
             }
             if (md->outputs[i]->instance_event_handler &&
@@ -1245,7 +1245,6 @@ void mdev_start_server(mapper_device md, int starting_port)
                 md->n_output_callbacks ++;
             }
         }
-        free(type);
         free(path);
     }
 }
@@ -1319,6 +1318,13 @@ mapper_db_device mdev_properties(mapper_device dev)
 void mdev_set_property(mapper_device dev, const char *property,
                        char type, void *value, int length)
 {
+    if (strcmp(property, "name") == 0 ||
+        strcmp(property, "host") == 0 ||
+        strcmp(property, "port") == 0 ||
+        strcmp(property, "user_data") == 0) {
+        trace("Cannot set locked device property '%s'\n", property);
+        return;
+    }
     mapper_table_add_or_update_typed_value(dev->props.extra, property,
                                            type, value, length);
 }
diff --git a/src/expression.c b/src/expression.c
index 7e42332..d6360f4 100644
--- a/src/expression.c
+++ b/src/expression.c
@@ -8,6 +8,7 @@
 
 #define MAX_HISTORY -100
 #define STACK_SIZE 128
+#define VAR_SIZE 8
 #ifdef DEBUG
 #define TRACING 0 /* Set non-zero to see trace during parse & eval. */
 #else
@@ -394,8 +395,8 @@ static struct {
     { "any",    1,      anyi,       anyf,       anyd        },
     { "mean",   1,      0,          meanf,      meand       },
     { "sum",    1,      sumi,       sumf,       sumd        },
-    { "vmax",   1,      vmaxi,      vmaxf,      vmaxd       },
-    { "vmin",   1,      vmini,      vminf,      vmind       },
+    { "max",    1,      vmaxi,      vmaxf,      vmaxd       },
+    { "min",    1,      vmini,      vminf,      vmind       },
 };
 
 typedef enum {
@@ -418,40 +419,46 @@ typedef enum {
     OP_BITWISE_OR,
     OP_LOGICAL_AND,
     OP_LOGICAL_OR,
-    OP_ASSIGNMENT,
     OP_CONDITIONAL_IF_THEN,
     OP_CONDITIONAL_IF_ELSE,
     OP_CONDITIONAL_IF_THEN_ELSE,
 } expr_op_t;
 
+#define NONE        0x0
+#define GET_ZERO    0x1
+#define GET_ONE     0x2
+#define GET_OPER    0x4
+#define ERROR       0x8
+
 static struct {
     const char *name;
-    unsigned int arity;
-    unsigned int precedence;
+    char arity;
+    char precedence;
+    uint16_t optimize_const_operands;
 } op_table[] = {
-    { "!",          1,  11 },
-    { "*",          2,  10 },
-    { "/",          2,  10 },
-    { "%",          2,  10 },
-    { "+",          2,   9 },
-    { "-",          2,   9 },
-    { "<<",         2,   8 },
-    { ">>",         2,   8 },
-    { ">",          2,   7 },
-    { ">=",         2,   7 },
-    { "<",          2,   7 },
-    { "<=",         2,   7 },
-    { "==",         2,   6 },
-    { "!=",         2,   6 },
-    { "&",          2,   5 },
-    { "^",          2,   4 },
-    { "|",          2,   3 },
-    { "&&",         2,   2 },
-    { "||",         2,   1 },
-    { "=",          2,   0 },
-    { "IFTHEN",     2,   0 },
-    { "IFELSE",     2,   0 },
-    { "IFTHENELSE", 3,   0 },
+    { "!",      1, 11, GET_ONE  | GET_ONE  <<4 | GET_ZERO <<8 | GET_ZERO <<12 },
+    { "*",      2, 10, GET_ZERO | GET_ZERO <<4 | GET_OPER <<8 | GET_OPER <<12 },
+    { "/",      2, 10, GET_ZERO | ERROR    <<4 | NONE     <<8 | GET_OPER <<12 },
+    { "%",      2, 10, GET_ZERO | GET_OPER <<4 | GET_ONE  <<8 | GET_OPER <<12 },
+    { "+",      2, 9,  GET_OPER | GET_OPER <<4 | NONE     <<8 | NONE     <<12 },
+    { "-",      2, 9,  NONE     | GET_OPER <<4 | NONE     <<8 | NONE     <<12 },
+    { "<<",     2, 8,  GET_ZERO | GET_OPER <<4 | NONE     <<8 | NONE     <<12 },
+    { ">>",     2, 8,  GET_ZERO | GET_OPER <<4 | NONE     <<8 | NONE     <<12 },
+    { ">",      2, 7,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { ">=",     2, 7,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "<",      2, 7,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "<=",     2, 7,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "==",     2, 6,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "!=",     2, 6,  NONE     | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "&",      2, 5,  GET_ZERO | GET_ZERO <<4 | NONE     <<8 | NONE     <<12 },
+    { "^",      2, 4,  GET_OPER | GET_OPER <<4 | NONE     <<8 | NONE     <<12 },
+    { "|",      2, 3,  GET_OPER | GET_OPER <<4 | GET_ONE  <<8 | GET_ONE  <<12 },
+    { "&&",     2, 2,  GET_ZERO | GET_ZERO <<4 | NONE     <<8 | NONE     <<12 },
+    { "||",     2, 1,  GET_OPER | GET_OPER <<4 | GET_ONE  <<8 | GET_ONE  <<12 },
+    // TODO: handle ternary operator
+    { "IFTHEN",      2, 0, NONE | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "IFELSE",      2, 0, NONE | NONE     <<4 | NONE     <<8 | NONE     <<12 },
+    { "IFTHENELSE",  3, 0, NONE | NONE     <<4 | NONE     <<8 | NONE     <<12 },
 };
 
 typedef int func_int32_arity0();
@@ -470,21 +477,22 @@ typedef double vfunc_double_arity1(mapper_signal_value_t*, int);
 typedef struct _token {
     enum {
         TOK_CONST           = 0x0001,
-        TOK_OP              = 0x0002,
-        TOK_VAR             = 0x0004,
-        TOK_FUNC            = 0x0008,
-        TOK_OPEN_PAREN      = 0x0010,
-        TOK_CLOSE_PAREN     = 0x0020,
-        TOK_OPEN_SQUARE     = 0x0040,
+        TOK_NEGATE          = 0x0002,
+        TOK_FUNC            = 0x0003,
+        TOK_VFUNC           = 0x0004,
+        TOK_OPEN_PAREN      = 0x0005,
+        TOK_OPEN_SQUARE     = 0x0010,
+        TOK_OPEN_CURLY      = 0x0020,
+        TOK_CLOSE_PAREN     = 0x0040,
         TOK_CLOSE_SQUARE    = 0x0080,
-        TOK_OPEN_CURLY      = 0x0100,
-        TOK_CLOSE_CURLY     = 0x0200,
-        TOK_COMMA           = 0x0400,
-        TOK_QUESTION        = 0x0800,
+        TOK_CLOSE_CURLY     = 0x0100,
+        TOK_VAR             = 0x0200,
+        TOK_OP              = 0x0400,
+        TOK_COMMA           = 0x0800,
         TOK_COLON           = 0x1000,
-        TOK_NEGATE          = 0x2000,
-        TOK_VECTORIZE       = 0x4000,
-        TOK_VFUNC           = 0x8000,
+        TOK_SEMICOLON       = 0x2000,
+        TOK_ASSIGNMENT      = 0x4000,
+        TOK_VECTORIZE,
         TOK_END,
     } toktype;
     union {
@@ -496,14 +504,31 @@ typedef struct _token {
         expr_func_t func;
         expr_vfunc_t vfunc;
     };
-    char datatype;
-    char casttype;
+    union {
+        char casttype;
+        int assignment_offset;
+    };
+    int vector_length;
+    union {
+        int vector_index;
+        int arity;
+    };
     char history_index;
     char vector_length_locked;
-    int vector_length;
-    int vector_index;
+    char datatype;
 } mapper_token_t, *mapper_token;
 
+typedef struct _variable {
+    char *name;
+    int vector_index;
+    int vector_length;
+    char datatype;
+    char casttype;
+    char history_size;
+    char vector_length_locked;
+    char assigned;
+} mapper_variable_t, *mapper_variable;
+
 static expr_func_t function_lookup(const char *s, int len)
 {
     int i;
@@ -534,6 +559,49 @@ static expr_var_t variable_lookup(const char *s, int len)
     return VAR_UNKNOWN;
 }
 
+static int const_tok_is_zero(mapper_token_t tok)
+{
+    switch (tok.datatype) {
+        case 'i':
+            return tok.i == 0;
+        case 'f':
+            return tok.f == 0.f;
+        case 'd':
+            return tok.d == 0.0;
+    }
+    return 0;
+}
+
+static int const_tok_is_one(mapper_token_t tok)
+{
+    switch (tok.datatype) {
+        case 'i':
+            return tok.i == 1;
+        case 'f':
+            return tok.f == 1.f;
+        case 'd':
+            return tok.d == 1.0;
+    }
+    return 0;
+}
+
+static int tok_arity(mapper_token_t tok)
+{
+    switch (tok.toktype) {
+        case TOK_OP:
+            return op_table[tok.op].arity;
+        case TOK_FUNC:
+            return function_table[tok.func].arity;
+        case TOK_VFUNC:
+            return vfunction_table[tok.func].arity;
+        case TOK_VECTORIZE:
+            return tok.arity;
+        default:
+            return 0;
+    }
+    return 0;
+}
+
 static int expr_lex(const char *str, int index, mapper_token_t *tok)
 {
     tok->datatype = 'i';
@@ -593,13 +661,17 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
             while (c && (isalpha(c) || isdigit(c)))
                 c = str[++index];
             tok->toktype = TOK_FUNC;
-            tok->func = function_lookup(str+i, index-i);
-            if (tok->func == FUNC_UNKNOWN) {
-                lex_error("unexpected `e' outside float\n");
-                break;
+            if ((tok->func = function_lookup(str+i, index-i)) != FUNC_UNKNOWN) {
+                tok->toktype = TOK_FUNC;
             }
-            else
-                return index;
+            else if ((tok->vfunc = vfunction_lookup(str+i, index-i)) != VFUNC_UNKNOWN) {
+                tok->toktype = TOK_VFUNC;
+            }
+            else {
+                tok->var = variable_lookup(str+i, index-i);
+                tok->toktype = TOK_VAR;
+            }
+            return index;
         }
         c = str[++index];
         if (c!='-' && c!='+' && !isdigit(c)) {
@@ -646,13 +718,15 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
         return ++index;
     case '=':
         // could be '=', '=='
-        tok->toktype = TOK_OP;
-        tok->op = OP_ASSIGNMENT;
         c = str[++index];
         if (c == '=') {
+            tok->toktype = TOK_OP;
             tok->op = OP_IS_EQUAL;
             ++index;
         }
+        else {
+            tok->toktype = TOK_ASSIGNMENT;
+        }
         return index;
     case '<':
         // could be '<', '<=', '<<'
@@ -747,7 +821,7 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
         return ++index;
     case '?':
         // conditional
-        tok->toktype = TOK_QUESTION;
+        tok->toktype = TOK_OP;
         tok->op = OP_CONDITIONAL_IF_THEN;
         c = str[++index];
         if (c == ':') {
@@ -758,6 +832,9 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
     case ':':
         tok->toktype = TOK_COLON;
         return ++index;
+    case ';':
+        tok->toktype = TOK_SEMICOLON;
+        return ++index;
     default:
         if (!isalpha(c)) {
             lex_error("unknown character '%c' in lexer\n", c);
@@ -765,18 +842,15 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
         }
         while (c && (isalpha(c) || isdigit(c)))
             c = str[++index];
-        if ((tok->var = variable_lookup(str+i, index-i)) != VAR_UNKNOWN) {
-            tok->toktype = TOK_VAR;
-        }
-        else if ((tok->func = function_lookup(str+i, index-i)) != FUNC_UNKNOWN) {
+        if ((tok->func = function_lookup(str+i, index-i)) != FUNC_UNKNOWN) {
             tok->toktype = TOK_FUNC;
         }
         else if ((tok->vfunc = vfunction_lookup(str+i, index-i)) != VFUNC_UNKNOWN) {
             tok->toktype = TOK_VFUNC;
         }
         else {
-            lex_error("Unknown variable or function name `%s'.\n",str+i);
-            break;
+            tok->var = variable_lookup(str+i, index-i);
+            tok->toktype = TOK_VAR;
         }
         return index;
     }
@@ -786,18 +860,28 @@ static int expr_lex(const char *str, int index, mapper_token_t *tok)
 
 struct _mapper_expr
 {
+    mapper_token tokens;
     mapper_token start;
     int length;
     int vector_size;
     int input_history_size;
     int output_history_size;
+    mapper_variable variables;
+    int num_variables;
     int constant_output;
 };
 
 void mapper_expr_free(mapper_expr expr)
 {
-    if (expr->start)
-        free(expr->start);
+    int i;
+    if (expr->tokens)
+        free(expr->tokens);
+    if (expr->num_variables && expr->variables) {
+        for (i = 0; i < expr->num_variables; i++) {
+            free(expr->variables[i].name);
+        }
+        free(expr->variables);
+    }
     free(expr);
 }
 
@@ -805,72 +889,89 @@ void mapper_expr_free(mapper_expr expr)
 
 void printtoken(mapper_token_t tok)
 {
-    int locked = tok.vector_length_locked;
+    int i;
+    char tokstr[32];
     switch (tok.toktype) {
-    case TOK_CONST:
-        switch (tok.datatype) {
-            case 'f':      printf("%ff%d", tok.f,
-                                  tok.vector_length);     break;
-            case 'd':      printf("%fd%d", tok.d,
-                                  tok.vector_length);     break;
-            case 'i':      printf("%di%d", tok.i,
-                                  tok.vector_length);     break;
-        }
-        if (locked)        printf("'");                   break;
-    case TOK_OP:           printf("%s%c%d%s",
-                                  op_table[tok.op].name,
-                                  tok.datatype,
-                                  tok.vector_length,
-                                  locked ? "'" : "");     break;
-    case TOK_OPEN_CURLY:   printf("{");                   break;
-    case TOK_OPEN_PAREN:   printf("(");                   break;
-    case TOK_CLOSE_PAREN:  printf(")");                   break;
-    case TOK_OPEN_SQUARE:  printf("[");                   break;
-    case TOK_CLOSE_SQUARE: printf("]");                   break;
-    case TOK_VAR:          printf("VAR(%s%c%d%s){%d}[%d]",
-                                  var_strings[tok.var], tok.datatype,
-                                  tok.vector_length,
-                                  locked ? "'" : "",
-                                  tok.history_index,
-                                  tok.vector_index);      break;
-    case TOK_FUNC:         printf("FUNC(%s)%c%d%s",
-                                  function_table[tok.func].name,
-                                  tok.datatype,
-                                  tok.vector_length,
-                                  locked ? "'" : "");     break;
-    case TOK_COMMA:        printf(",");                   break;
-    case TOK_QUESTION:     printf("?");                   break;
-    case TOK_COLON:        printf(":");                   break;
-    case TOK_VECTORIZE:    printf("VECT%c%d(%d)",
-                                  tok.datatype,
-                                  tok.vector_length,
-                                  tok.vector_index);      break;
-    case TOK_NEGATE:       printf("-");                   break;
-    case TOK_VFUNC:        printf("VFUNC(%s)%c%d",
-                                  vfunction_table[tok.func].name,
-                                  tok.datatype,
-                                  tok.vector_length);     break;
-    case TOK_END:          printf("END");                 break;
-    default:               printf("(unknown token)");     break;
+        case TOK_CONST:
+            switch (tok.datatype) {
+                case 'f':   snprintf(tokstr, 32, "%f", tok.f);  break;
+                case 'd':   snprintf(tokstr, 32, "%f", tok.d);  break;
+                case 'i':   snprintf(tokstr, 32, "%d", tok.i);  break;
+            }                                                   break;
+        case TOK_OP:
+            snprintf(tokstr, 32, "%s", op_table[tok.op].name);  break;
+        case TOK_OPEN_CURLY:    snprintf(tokstr, 32, "{");      break;
+        case TOK_OPEN_PAREN:    snprintf(tokstr, 32, "(");      break;
+        case TOK_OPEN_SQUARE:   snprintf(tokstr, 32, "[");      break;
+        case TOK_CLOSE_CURLY:   snprintf(tokstr, 32, "}");      break;
+        case TOK_CLOSE_PAREN:   snprintf(tokstr, 32, ")");      break;
+        case TOK_CLOSE_SQUARE:  snprintf(tokstr, 32, "]");      break;
+        case TOK_VAR:
+            if (tok.var<N_VARS)
+                snprintf(tokstr, 32, "%s{%d}[%d]", var_strings[tok.var],
+                         tok.history_index, tok.vector_index);
+            else
+                snprintf(tokstr, 32, "var%d{%d}[%d]", tok.var-N_VARS,
+                         tok.history_index, tok.vector_index);
+            break;
+        case TOK_FUNC:
+            snprintf(tokstr, 32, "%s()", function_table[tok.func].name);
+            break;
+        case TOK_COMMA:         snprintf(tokstr, 32, ",");      break;
+        case TOK_COLON:         snprintf(tokstr, 32, ":");      break;
+        case TOK_VECTORIZE:
+            snprintf(tokstr, 32, "VECT(%d)", tok.arity);
+            break;
+        case TOK_NEGATE:        snprintf(tokstr, 32, "-");      break;
+        case TOK_VFUNC:
+            snprintf(tokstr, 32, "%s()", vfunction_table[tok.func].name);
+            break;
+        case TOK_ASSIGNMENT:
+            if (tok.var<N_VARS)
+                snprintf(tokstr, 32, "ASSIGN_TO:%s{%d}[%d]->[%d]",
+                         var_strings[tok.var], tok.history_index,
+                         tok.assignment_offset, tok.vector_index);
+            else
+                snprintf(tokstr, 32, "ASSIGN_TO:var%d{%d}[%d]->[%d]",
+                         tok.var-N_VARS, tok.history_index,
+                         tok.assignment_offset, tok.vector_index);
+            break;
+        case TOK_END:       printf("END\n");                    return;
+        default:            printf("(unknown token)");          return;
+    }
+    printf("%s", tokstr);
+    // indent
+    int len = strlen(tokstr);
+    for (i = len; i < 30; i++) {
+        printf(" ");
     }
+    printf("%c[%d]", tok.datatype, tok.vector_length);
+    if (tok.vector_length_locked)
+        printf("L");
     if (tok.casttype)
         printf("->%c", tok.casttype);
+    printf("\n");
 }
 
 void printstack(const char *s, mapper_token_t *stack, int top)
 {
-    int i;
+    int i, j, indent = 0;
     printf("%s ", s);
+    if (s)
+        indent = strlen(s) + 1;
     for (i=0; i<=top; i++) {
+        if (i != 0) {
+            for (j=0; j<indent; j++)
+                printf(" ");
+        }
         printtoken(stack[i]);
-        printf(" ");
     }
     printf("\n");
 }
 
 void printexpr(const char *s, mapper_expr e)
 {
-    printstack(s, e->start, e->length-1);
+    printstack(s, e->tokens, e->length-1);
 }
 
 #endif
@@ -878,47 +979,75 @@ void printexpr(const char *s, mapper_expr e)
 static char compare_token_datatype(mapper_token_t tok, char type)
 {
     // return the higher datatype
-    if (tok.datatype == 'd' || tok.casttype == 'd' || type == 'd')
+    if (tok.datatype == 'd' || type == 'd')
         return 'd';
-    else if (tok.datatype == 'f' || tok.casttype == 'f' || type == 'f')
+    else if (tok.datatype == 'f' || type == 'f')
         return 'f';
     else
         return 'i';
 }
 
-static void promote_token_datatype(mapper_token_t *tok, char type)
+static char promote_token_datatype(mapper_token_t *tok, char type)
 {
+    tok->casttype = 0;
+
     if (tok->datatype == type)
-        return;
+        return type;
+
+    if (tok->toktype == TOK_ASSIGNMENT) {
+        if (tok->var < N_VARS) {
+            // typecasting is not possible
+            return tok->datatype;
+        }
+        else {
+            // user-defined variable, can typecast
+            tok->casttype = type;
+            return type;
+        }
+    }
 
     if (tok->toktype == TOK_CONST) {
         // constants can be cast immediately
         if (tok->datatype == 'i') {
             if (type == 'f') {
                 tok->f = (float)tok->i;
-                tok->datatype = 'f';
+                tok->datatype = type;
             }
             else if (type == 'd') {
                 tok->d = (double)tok->i;
-                tok->datatype = 'd';
+                tok->datatype = type;
             }
         }
         else if (tok->datatype == 'f') {
             if (type == 'd') {
                 tok->d = (double)tok->f;
-                tok->datatype = 'd';
+                tok->datatype = type;
+            }
+            else if (type == 'i') {
+                tok->casttype = type;
             }
         }
+        else {
+            tok->casttype = type;
+        }
+        return type;
     }
     else if (tok->toktype == TOK_VAR) {
         // we need to cast at runtime
-        if (tok->datatype == 'i' || type == 'd')
-            tok->casttype = type;
+        tok->casttype = type;
+        return type;
     }
     else {
-        if (tok->datatype == 'i' || type == 'd')
+        if (tok->datatype == 'i' || type == 'd') {
             tok->datatype = type;
+            return type;
+        }
+        else {
+            tok->casttype = type;
+            return tok->datatype;
+        }
     }
+    return type;
 }
 
 static void lock_vector_lengths(mapper_token_t *stack, int top)
@@ -932,40 +1061,87 @@ static void lock_vector_lengths(mapper_token_t *stack, int top)
         else if (stack[i].toktype == TOK_FUNC)
             arity += function_table[stack[i].func].arity;
         else if (stack[i].toktype == TOK_VECTORIZE)
-            arity += stack[i].vector_index;
+            arity += stack[i].arity;
         i--;
     }
 }
 
+static int precompute(mapper_token_t *stack, int length, int vector_length)
+{
+    struct _mapper_expr e;
+    e.start = stack;
+    e.length = length;
+    e.vector_size = vector_length;
+    e.variables = 0;
+    e.num_variables = 0;
+    mapper_signal_history_t h;
+    // TODO: this variable should not be mapper_signal_value_t?
+    mapper_signal_value_t v;
+    h.type = stack[length-1].datatype;
+    h.value = &v;
+    h.position = -1;
+    h.length = 1;
+    h.size = 1;
+
+    if (!mapper_expr_evaluate(&e, 0, 0, &h, 0))
+        return 0;
+
+    switch (stack[length-1].datatype) {
+        case 'f':
+            stack[0].f = v.f;
+            break;
+        case 'd':
+            stack[0].d = v.d;
+            break;
+        case 'i':
+            stack[0].i = v.i32;
+            break;
+        default:
+            return 0;
+        break;
+    }
+    stack[0].toktype = TOK_CONST;
+    stack[0].datatype = stack[length-1].datatype;
+    return length-1;
+}
+
 static int check_types_and_lengths(mapper_token_t *stack, int top)
 {
     // TODO: allow precomputation of const-only vectors
-    int i, arity, can_precompute = 1;
+    int i, arity, can_precompute = 1, optimize = NONE;
     char type = stack[top].datatype;
     int vector_length = stack[top].vector_length;
 
-    if (stack[top].toktype == TOK_OP)
-        arity = op_table[stack[top].op].arity;
-    else if (stack[top].toktype == TOK_FUNC) {
-        arity = function_table[stack[top].func].arity;
-        if (stack[top].func >= FUNC_UNIFORM)
+    switch (stack[top].toktype) {
+        case TOK_OP:
+            arity = op_table[stack[top].op].arity;
+            break;
+        case TOK_FUNC:
+            arity = function_table[stack[top].func].arity;
+            if (stack[top].func >= FUNC_UNIFORM)
+                can_precompute = 0;
+            break;
+        case TOK_VFUNC:
+            arity = vfunction_table[stack[top].func].arity;
+            break;
+        case TOK_VECTORIZE:
+            arity = stack[top].arity;
             can_precompute = 0;
+            break;
+        case TOK_ASSIGNMENT:
+            arity = 1;
+            can_precompute = 0;
+            break;
+        default:
+            return top;
     }
-    else if (stack[top].toktype == TOK_VFUNC) {
-        arity = vfunction_table[stack[top].func].arity;
-    }
-    else if (stack[top].toktype == TOK_VECTORIZE) {
-        arity = stack[top].vector_index;
-        can_precompute = 0;
-    }
-    else
-        return top;
 
     if (arity) {
         // find operator or function inputs
         i = top;
         int skip = 0;
         int depth = arity;
+        int operand;
         // last arg of op or func is at top-1
         type = compare_token_datatype(stack[top-1], type);
         if (stack[top-1].vector_length > vector_length)
@@ -981,6 +1157,29 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
                 can_precompute = 0;
 
             if (skip == 0) {
+                if (stack[i].toktype == TOK_CONST
+                    && stack[top].toktype == TOK_OP
+                    && depth <= op_table[stack[top].op].arity) {
+                    if (const_tok_is_zero(stack[i])) {
+                        // mask and bitshift, depth == 1 or 2
+                        optimize = (op_table[stack[top].op].optimize_const_operands
+                                    >>(depth-1)*4) & 0xF;
+                    }
+                    else if (const_tok_is_one(stack[i])) {
+                        optimize = (op_table[stack[top].op].optimize_const_operands
+                                    >>(depth+1)*4) & 0xF;
+                    }
+                    if (optimize == GET_OPER) {
+                        if (i == top-1) {
+                            // optimize immediately without moving other operand
+                            return top-2;
+                        }
+                        else {
+                            // store position of non-zero operand
+                            operand = top-1;
+                        }
+                    }
+                }
                 type = compare_token_datatype(stack[i], type);
                 if (stack[i].toktype == TOK_VFUNC)
                     stack[i].vector_length = vector_length;
@@ -999,17 +1198,55 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
             else if (stack[i].toktype == TOK_VFUNC)
                 skip += vfunction_table[stack[i].func].arity;
             else if (stack[i].toktype == TOK_VECTORIZE)
-                skip += stack[i].vector_index;
+                skip += stack[i].arity;
         }
 
         if (depth)
             return -1;
 
+        if (!can_precompute) {
+            switch (optimize) {
+                case ERROR:
+                {
+                    parse_error("Operator '%s' cannot have zero operand.\n",
+                                op_table[stack[top].op].name);
+                    return -1;
+                }
+                case GET_ZERO:
+                case GET_ONE:
+                {
+                    // finish walking down compound arity
+                    int _arity = 0;
+                    while ((_arity += tok_arity(stack[i])) && i >= 0) {
+                        _arity--;
+                        i--;
+                    }
+                    stack[i].toktype = TOK_CONST;
+                    stack[i].datatype = 'i';
+                    stack[i].i = optimize == GET_ZERO ? 0 : 1;
+                    stack[i].vector_length = vector_length;
+                    stack[i].vector_length_locked = 0;
+                    stack[i].casttype = 0;
+                    return i;
+                }
+                case GET_OPER:
+                    // copy tokens for non-zero operand
+                    for (; i < operand; i++) {
+                        // shunt tokens down stack
+                        memcpy(stack+i, stack+i+1, sizeof(mapper_token_t));
+                    }
+                    // we may need to promote vector length, so do not return yet
+                    top = operand-1;
+                default:
+                    break;
+            }
+        }
+
         /* walk down stack distance of arity again, promoting datatypes
          * and vector lengths */
         i = top;
         if (stack[top].toktype == TOK_VECTORIZE) {
-            skip = stack[top].vector_index;
+            skip = stack[top].arity;
             depth = 0;
         }
         else if (stack[top].toktype == TOK_VFUNC) {
@@ -1020,14 +1257,22 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
             skip = 0;
             depth = arity;
         }
+        type = promote_token_datatype(&stack[i], type);
         while (--i >= 0) {
             // we will promote types within range of compound arity
-            promote_token_datatype(&stack[i], type);
+            type = promote_token_datatype(&stack[i], type);
 
             if (skip <= 0) {
                 // also check/promote vector length
                 if (!stack[i].vector_length_locked) {
-                    if (stack[i].toktype != TOK_VFUNC)
+                    if (stack[i].toktype == TOK_VFUNC) {
+                        if (stack[i].vector_length != vector_length) {
+                            parse_error("Vector length mismatch (%d != %d).\n",
+                                        stack[i].vector_length, vector_length);
+                            return -1;
+                        }
+                    }
+                    else
                         stack[i].vector_length = vector_length;
                 }
                 else if (stack[i].vector_length != vector_length) {
@@ -1052,7 +1297,7 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
             else if (stack[i].toktype == TOK_VFUNC)
                 skip = 2;
             else if (stack[i].toktype == TOK_VECTORIZE)
-                skip = stack[i].vector_index+1;
+                skip = stack[i].arity + 1;
 
             if (skip > 0)
                 skip--;
@@ -1061,7 +1306,6 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
             if (depth <= 0 && skip <= 0)
                 break;
         }
-        stack[top].datatype = type;
 
         if (!stack[top].vector_length_locked) {
             if (stack[top].toktype != TOK_VFUNC)
@@ -1069,7 +1313,7 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
         }
         else if (stack[top].vector_length != vector_length) {
             parse_error("Vector length mismatch (%d != %d).\n",
-                        stack[i].vector_length, vector_length);
+                        stack[top].vector_length, vector_length);
             return -1;
         }
     }
@@ -1078,43 +1322,67 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
     }
 
     // if stack within bounds of arity was only constants, we're ok to compute
-    if (!can_precompute)
+    if (can_precompute)
+        return top - precompute(&stack[top-arity], arity+1, vector_length);
+    else
         return top;
+}
 
-    struct _mapper_expr e;
-    e.start = &stack[top-arity];
-    e.length = arity+1;
-    e.vector_size = vector_length;
-    mapper_signal_history_t h;
-    mapper_signal_value_t v;
-    h.type = stack[top].datatype;
-    h.value = &v;
-    h.position = -1;
-    h.length = 1;
-    h.size = 1;
-    if (!mapper_expr_evaluate(&e, 0, &h))
-        return top;
+static int check_assignment_types_and_lengths(mapper_token_t *stack, int top)
+{
+    int i = top, vector_length = 0;
+    expr_var_t var = stack[top].var;
 
-    switch (stack[top].datatype) {
-        case 'f':
-            stack[top-arity].f = v.f;
-            break;
-        case 'd':
-            stack[top-arity].d = v.d;
-            break;
-        case 'i':
-            stack[top-arity].i = v.i32;
-            break;
-        default:
-            return 0;
-            break;
+    while (i >= 0 && stack[i].toktype == TOK_ASSIGNMENT) {
+        if (stack[i].var != var) {
+            parse_error("Cannot mix variable references in assignment\n");
+            return -1;
+        }
+        vector_length += stack[i].vector_length;
+        i--;
+    }
+    if (i < 0) {
+        parse_error("Malformed expression (1).");
+        return -1;
+    }
+    if (stack[i].vector_length != vector_length) {
+        parse_error("Vector length mismatch (%d != %d).\n",
+                    stack[i].vector_length, vector_length);
+        return -1;
     }
-    stack[top-arity].toktype = TOK_CONST;
-    return top-arity;
+    promote_token_datatype(&stack[i], stack[top].datatype);
+    if (check_types_and_lengths(stack, i) == -1)
+        return -1;
+    promote_token_datatype(&stack[i], stack[top].datatype);
+
+    if (stack[top].history_index == 0 || i == 0)
+        return 0;
+
+    // Move assignment expression to beginning of stack
+    int j = 0, expr_length = top-i+1;
+    if (stack[i].toktype == TOK_VECTORIZE)
+        expr_length += stack[i].arity;
+
+    mapper_token_t temp[expr_length];
+    for (i = top-expr_length+1; i <= top; i++)
+        memcpy(&temp[j++], &stack[i], sizeof(mapper_token_t));
+
+    for (i = top-expr_length; i >= 0; i--)
+        memcpy(&stack[i+expr_length], &stack[i], sizeof(mapper_token_t));
+
+    for (i = 0; i < expr_length; i++)
+        memcpy(&stack[i], &temp[i], sizeof(mapper_token_t));
+
+    return 0;
 }
 
 /* Macros to help express stack operations in parser. */
-#define FAIL(msg) { parse_error("%s\n", msg); return 0; }
+#define FAIL(msg) {                                                 \
+    parse_error("%s\n", msg);                                       \
+    while (--num_variables >= 0)                                    \
+        free(variables[num_variables].name);                        \
+    return 0;                                                       \
+}
 #define PUSH_TO_OUTPUT(x)                                           \
 {                                                                   \
     if (++outstack_index >= STACK_SIZE)                             \
@@ -1130,14 +1398,11 @@ static int check_types_and_lengths(mapper_token_t *stack, int top)
 #define POP_OPERATOR() ( opstack_index-- )
 #define POP_OPERATOR_TO_OUTPUT()                                    \
 {                                                                   \
-    if (opstack[opstack_index].toktype == TOK_QUESTION) {           \
-        opstack[opstack_index].toktype = TOK_OP;                    \
-    }                                                               \
     PUSH_TO_OUTPUT(opstack[opstack_index]);                         \
     outstack_index = check_types_and_lengths(outstack,              \
                                              outstack_index);       \
     if (outstack_index < 0)                                         \
-         {FAIL("Malformed expression.");}                           \
+         {FAIL("Malformed expression (2).");}                       \
     POP_OPERATOR();                                                 \
 }
 #define GET_NEXT_TOKEN(x)                                           \
@@ -1152,9 +1417,7 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                                         char input_type,
                                         char output_type,
                                         int input_vector_size,
-                                        int output_vector_size,
-                                        int *input_history_size,
-                                        int *output_history_size)
+                                        int output_vector_size)
 {
     if (!str) return 0;
     if (input_type != 'i' && input_type != 'f' && input_type != 'd') return 0;
@@ -1162,48 +1425,110 @@ mapper_expr mapper_expr_new_from_string(const char *str,
 
     mapper_token_t outstack[STACK_SIZE];
     mapper_token_t opstack[STACK_SIZE];
-    int lex_index = 0, outstack_index = -1, opstack_index = -1;
+    int i, lex_index = 0, outstack_index = -1, opstack_index = -1;
     int oldest_input = 0, oldest_output = 0, max_vector = 1;
 
+    int assigning = 0;
+    int output_assigned = 0;
     int vectorizing = 0;
     int variable = 0;
     int allow_toktype = 0xFFFF;
     int constant_output = 1;
+
+    mapper_variable_t variables[VAR_SIZE];
+    int num_variables = 0;
+
+    int assign_mask = TOK_VAR | TOK_OPEN_SQUARE | TOK_COMMA | TOK_CLOSE_SQUARE;
+    int OBJECT_TOKENS = (TOK_VAR | TOK_CONST | TOK_FUNC | TOK_VFUNC
+                         | TOK_NEGATE | TOK_OPEN_PAREN | TOK_OPEN_SQUARE);
+
     mapper_token_t tok;
 
-    // all expressions must start with "y=" (ignoring spaces)
-    if (str[lex_index++] != 'y') return 0;
+    // all expressions must start with assignment e.g. "y=" (ignoring spaces)
     while (str[lex_index] == ' ') lex_index++;
-    if (str[lex_index++] != '=') return 0;
+    if (!str[lex_index])
+        {FAIL("No expression found.");}
+
+    assigning = 1;
+    allow_toktype = TOK_VAR | TOK_OPEN_SQUARE;
 
     while (str[lex_index]) {
         GET_NEXT_TOKEN(tok);
         if (variable && tok.toktype != TOK_OPEN_SQUARE
             && tok.toktype != TOK_OPEN_CURLY)
             variable = 0;
-        if (!(tok.toktype & allow_toktype))
+        if (!((tok.toktype & allow_toktype & (assigning ? assign_mask : 0xFFFF))
+              | (assigning ? TOK_ASSIGNMENT : 0))) {
             {FAIL("Illegal token sequence.");}
+        }
         switch (tok.toktype) {
             case TOK_CONST:
                 // push to output stack
                 PUSH_TO_OUTPUT(tok);
-                allow_toktype = TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE |
-                                TOK_COMMA | TOK_QUESTION | TOK_COLON;
+                allow_toktype = (TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE
+                                 | TOK_COMMA | TOK_COLON | TOK_SEMICOLON);
                 break;
             case TOK_VAR:
                 // set datatype
-                tok.datatype = tok.var < VAR_Y ? input_type : output_type;
-                if (tok.var == VAR_X)
+                if (tok.var == VAR_X) {
+                    tok.datatype = input_type;
+                    tok.vector_length = input_vector_size;
+                    tok.vector_length_locked = 1;
                     constant_output = 0;
+                }
+                else if (tok.var == VAR_Y) {
+                    tok.datatype = output_type;
+                    tok.vector_length = output_vector_size;
+                    tok.vector_length_locked = 1;
+                }
+                else {
+                    // get name of variable
+                    int index = lex_index-1;
+                    char c = str[index];
+                    while (index >= 0 && c && (isalpha(c) || isdigit(c))) {
+                        if (--index >= 0)
+                            c = str[index];
+                    }
+
+                    // check if variable name matches known variable
+                    int i;
+                    for (i = 0; i < num_variables; i++) {
+                        if (strncmp(variables[i].name, str+index+1, lex_index-index-1)==0) {
+                            tok.var = i + N_VARS;
+                            tok.datatype = variables[i].datatype;
+                            tok.vector_length = variables[i].vector_length;
+                            break;
+                        }
+                    }
+
+                    if (i == num_variables) {
+                        if (num_variables >= VAR_SIZE)
+                            {FAIL("Maximum number of variables exceeded.");}
+                        // need to store new variable
+                        variables[num_variables].name = malloc(lex_index-index);
+                        snprintf(variables[num_variables].name, lex_index-index,
+                                 "%s", str+index+1);
+                        variables[num_variables].datatype = 'd';
+                        variables[num_variables].vector_length = 1;
+                        variables[num_variables].history_size = 1;
+                        variables[num_variables].assigned = 0;
+#if TRACING
+                        printf("Stored new variable '%s' at index %i\n",
+                               variables[num_variables].name, num_variables);
+#endif
+                        tok.var = num_variables + N_VARS;
+                        tok.datatype = 'd';
+                        num_variables++;
+                    }
+                }
                 tok.history_index = 0;
                 tok.vector_index = 0;
-                tok.vector_length = tok.var < VAR_Y ? input_vector_size : output_vector_size;
-                tok.vector_length_locked = 1;
                 PUSH_TO_OUTPUT(tok);
                 // variables can have vector and history indices
                 variable = TOK_OPEN_SQUARE | TOK_OPEN_CURLY;
-                allow_toktype = TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE |
-                                TOK_COMMA | TOK_QUESTION | TOK_COLON | variable;
+                allow_toktype = (TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE
+                                 | TOK_COMMA | TOK_COLON | TOK_SEMICOLON
+                                 | variable | (assigning ? TOK_ASSIGNMENT : 0));
                 break;
             case TOK_FUNC:
                 if (function_table[tok.func].func_int32)
@@ -1217,8 +1542,8 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 if (function_table[tok.func].arity)
                     allow_toktype = TOK_OPEN_PAREN;
                 else
-                    allow_toktype = TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE |
-                                    TOK_COMMA | TOK_QUESTION | TOK_COLON;
+                    allow_toktype = (TOK_OP | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE
+                                     | TOK_COMMA | TOK_COLON | TOK_SEMICOLON);
                 if (tok.func >= FUNC_UNIFORM)
                     constant_output = 0;
                 break;
@@ -1231,9 +1556,9 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 allow_toktype = TOK_OPEN_PAREN;
                 break;
             case TOK_OPEN_PAREN:
+                tok.arity = 1;
                 PUSH_TO_OPERATOR(tok);
-                allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC |
-                                TOK_NEGATE | TOK_OPEN_PAREN | TOK_OPEN_SQUARE;
+                allow_toktype = OBJECT_TOKENS;
                 break;
             case TOK_CLOSE_PAREN:
                 // pop from operator stack to output until left parenthesis found
@@ -1242,17 +1567,30 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 }
                 if (opstack_index < 0)
                     {FAIL("Unmatched parentheses or misplaced comma.");}
+
+                int arity = opstack[opstack_index].arity;
                 // remove left parenthesis from operator stack
-                if (tok.toktype == TOK_CLOSE_PAREN) {
-                    POP_OPERATOR();
-                    if (opstack[opstack_index].toktype == TOK_FUNC ||
-                        opstack[opstack_index].toktype == TOK_VFUNC) {
-                        // if stack[top] is tok_func or tok_vfunc, pop to output
-                        POP_OPERATOR_TO_OUTPUT();
+                POP_OPERATOR();
+
+                // if stack[top] is tok_func or tok_vfunc, pop to output
+                if (opstack[opstack_index].toktype == TOK_FUNC) {
+                    // check for overloaded functions
+                    if (arity == 1) {
+                        if (opstack[opstack_index].func == FUNC_MIN) {
+                            opstack[opstack_index].toktype = TOK_VFUNC;
+                            opstack[opstack_index].vfunc = VFUNC_MIN;
+                        }
+                        else if (opstack[opstack_index].func == FUNC_MAX) {
+                            opstack[opstack_index].toktype = TOK_VFUNC;
+                            opstack[opstack_index].vfunc = VFUNC_MAX;
+                        }
                     }
+                    POP_OPERATOR_TO_OUTPUT();
                 }
-                allow_toktype = TOK_OP | TOK_QUESTION | TOK_COLON | TOK_COMMA |
-                                TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE;
+                else if (opstack[opstack_index].toktype == TOK_VFUNC)
+                    POP_OPERATOR_TO_OUTPUT();
+                allow_toktype = (TOK_OP | TOK_COLON | TOK_SEMICOLON | TOK_COMMA
+                                 | TOK_CLOSE_PAREN | TOK_CLOSE_SQUARE);
                 break;
             case TOK_COMMA:
                 // pop from operator stack to output until left parenthesis or TOK_VECTORIZE found
@@ -1261,16 +1599,21 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                        && opstack[opstack_index].toktype != TOK_VECTORIZE) {
                     POP_OPERATOR_TO_OUTPUT();
                 }
-                if (opstack_index < 0)
-                    {FAIL("Unmatched parentheses, brackets or misplaced comma.");}
+                if (opstack_index < 0) {
+                    {FAIL("Malformed expression (3).");}
+                    break;
+                }
                 if (opstack[opstack_index].toktype == TOK_VECTORIZE) {
                     opstack[opstack_index].vector_index++;
                     opstack[opstack_index].vector_length +=
                         outstack[outstack_index].vector_length;
                     lock_vector_lengths(outstack, outstack_index);
                 }
-                allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC |
-                                TOK_NEGATE | TOK_OPEN_PAREN | TOK_OPEN_SQUARE;
+                else {
+                    // open paren
+                    opstack[opstack_index].arity++;
+                }
+                allow_toktype = OBJECT_TOKENS;
                 break;
             case TOK_COLON:
                 // pop from operator stack to output until conditional found
@@ -1282,25 +1625,48 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 if (opstack_index < 0)
                     {FAIL("Unmatched colon.");}
                 opstack[opstack_index].op = OP_CONDITIONAL_IF_THEN_ELSE;
-                allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC |
-                                TOK_NEGATE | TOK_OPEN_PAREN | TOK_OPEN_SQUARE;
+                allow_toktype = OBJECT_TOKENS;
+                break;
+            case TOK_SEMICOLON:
+                while (opstack_index >= 0) {
+                    POP_OPERATOR_TO_OUTPUT();
+                }
+                if (opstack_index > 0)
+                    {FAIL("Malformed expression (4).");}
+                // check if last expression was assigned correcty
+                if (outstack[outstack_index].toktype != TOK_ASSIGNMENT)
+                    {FAIL("Malformed expression (5).");}
+                if (check_assignment_types_and_lengths(outstack, outstack_index) == -1)
+                    {FAIL("Malformed expression (6).");}
+                // start another sub-expression
+                assigning = 1;
+                allow_toktype = TOK_VAR;
                 break;
-            case TOK_QUESTION:
-                tok.toktype = TOK_OP;
-                // lexer has already set op for either '?' or "?:"
             case TOK_OP:
                 // check precedence of operators on stack
                 while (opstack_index >= 0 && opstack[opstack_index].toktype == TOK_OP
                        && op_table[opstack[opstack_index].op].precedence >=
                        op_table[tok.op].precedence) {
+                    /* If operands are constants, we will pop operators of equal
+                     * precendence to the output stack to allow precomputation,
+                     * otherwise only pop operators with greater precedence.
+                     * This results in a more efficient representation. */
+                    int i, consts_only = 1;
+                    for (i = 0; i < op_table[opstack[opstack_index].op].arity; i++) {
+                        if (outstack[outstack_index-i].toktype != TOK_CONST)
+                            consts_only = 0;
+                    }
+                    if (!consts_only
+                        && op_table[opstack[opstack_index].op].precedence ==
+                        op_table[tok.op].precedence)
+                        break;
                     POP_OPERATOR_TO_OUTPUT();
                 }
                 PUSH_TO_OPERATOR(tok);
-                allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC |
-                                TOK_NEGATE | TOK_OPEN_PAREN | TOK_OPEN_SQUARE;
+                allow_toktype = OBJECT_TOKENS;
                 break;
             case TOK_OPEN_SQUARE:
-                if (variable & TOK_OPEN_SQUARE) {
+                if (variable & TOK_OPEN_SQUARE) { // vector index not set
                     GET_NEXT_TOKEN(tok);
                     if (tok.toktype != TOK_CONST || tok.datatype != 'i')
                         {FAIL("Non-integer vector index.");}
@@ -1335,21 +1701,19 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                         {FAIL("Unmatched bracket.");}
                     // vector index set
                     variable &= ~TOK_OPEN_SQUARE;
-                    allow_toktype = TOK_OP | TOK_COMMA | TOK_CLOSE_PAREN |
-                                    TOK_CLOSE_SQUARE | TOK_QUESTION |
-                                    TOK_COLON | variable;
+                    allow_toktype = (TOK_OP | TOK_COMMA | TOK_CLOSE_PAREN
+                                     | TOK_CLOSE_SQUARE | TOK_COLON | TOK_SEMICOLON
+                                     | variable | (assigning ? TOK_ASSIGNMENT : 0));
                 }
                 else {
                     if (vectorizing)
                         {FAIL("Nested (multidimensional) vectors not allowed.");}
                     tok.toktype = TOK_VECTORIZE;
                     tok.vector_length = 0;
-                    // use vector index for tracking arity of tok
-                    tok.vector_index = 0;
+                    tok.arity = 0;
                     PUSH_TO_OPERATOR(tok);
                     vectorizing = 1;
-                    allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC |
-                                    TOK_OPEN_PAREN | TOK_NEGATE;
+                    allow_toktype = OBJECT_TOKENS & ~TOK_OPEN_SQUARE;
                 }
                 break;
             case TOK_CLOSE_SQUARE:
@@ -1362,7 +1726,7 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                     {FAIL("Unmatched brackets or misplaced comma.");}
                 if (opstack[opstack_index].vector_length) {
                     opstack[opstack_index].vector_length_locked = 1;
-                    opstack[opstack_index].vector_index++;
+                    opstack[opstack_index].arity++;
                     opstack[opstack_index].vector_length +=
                         outstack[outstack_index].vector_length;
                     lock_vector_lengths(outstack, outstack_index);
@@ -1373,11 +1737,11 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                     POP_OPERATOR();
                 }
                 vectorizing = 0;
-                allow_toktype = TOK_OP | TOK_CLOSE_PAREN | TOK_COMMA |
-                                TOK_QUESTION | TOK_COLON;
+                allow_toktype = (TOK_OP | TOK_CLOSE_PAREN | TOK_COMMA
+                                 | TOK_COLON | TOK_SEMICOLON);
                 break;
             case TOK_OPEN_CURLY:
-                if (!(variable & TOK_OPEN_CURLY))
+                if (!(variable & TOK_OPEN_CURLY)) // history index already set
                     {FAIL("Misplaced brace.");}
                 GET_NEXT_TOKEN(tok);
                 if (tok.toktype == TOK_NEGATE) {
@@ -1388,18 +1752,18 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 if (tok.toktype != TOK_CONST || tok.datatype != 'i')
                     {FAIL("Non-integer history index.");}
                 outstack[outstack_index].history_index *= tok.i;
-                if (outstack[outstack_index].var == VAR_X) {
-                    if (outstack[outstack_index].history_index > 0)
-                        {FAIL("Input history index cannot be > 0.");}
-                    else if (outstack[outstack_index].history_index < MAX_HISTORY)
-                        {FAIL("Input history index cannot be < -100.");}
-                }
-                else if (outstack[outstack_index].var == VAR_Y) {
+                if (outstack[outstack_index].var == VAR_Y) {
                     if (outstack[outstack_index].history_index > -1)
                         {FAIL("Output history index cannot be > -1.");}
                     else if (outstack[outstack_index].history_index < MAX_HISTORY)
                         {FAIL("Output history index cannot be < -100.");}
                 }
+                else {
+                    if (outstack[outstack_index].history_index > 0)
+                    {FAIL("Input history index cannot be > 0.");}
+                    else if (outstack[outstack_index].history_index < MAX_HISTORY)
+                    {FAIL("Input history index cannot be < -100.");}
+                }
 
                 if (outstack[outstack_index].var == VAR_X
                     && outstack[outstack_index].history_index < oldest_input)
@@ -1407,25 +1771,107 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                 else if (outstack[outstack_index].var == VAR_Y
                          && outstack[outstack_index].history_index < oldest_output)
                     oldest_output = outstack[outstack_index].history_index;
+                else if (outstack[outstack_index].var >= N_VARS) {
+                    // user-defined variable
+                    int var_idx = outstack[outstack_index].var - N_VARS;
+                    int hist_idx = outstack[outstack_index].history_index;
+                    if ((hist_idx * -1 + 1) > variables[var_idx].history_size)
+                        variables[var_idx].history_size = hist_idx * -1 + 1;
+                }
                 GET_NEXT_TOKEN(tok);
                 if (tok.toktype != TOK_CLOSE_CURLY)
                     {FAIL("Unmatched brace.");}
                 variable &= ~TOK_OPEN_CURLY;
-                allow_toktype = TOK_OP | TOK_COMMA | TOK_CLOSE_PAREN |
-                                TOK_CLOSE_SQUARE | TOK_QUESTION |
-                                TOK_COLON | variable;
+                allow_toktype = (TOK_OP | TOK_COMMA | TOK_CLOSE_PAREN
+                                 | TOK_CLOSE_SQUARE | TOK_COLON | TOK_SEMICOLON
+                                 | variable | (assigning ? TOK_ASSIGNMENT : 0));
                 break;
             case TOK_NEGATE:
-                // push '0' to output stack, and '-' to operator stack
+                // push '-1' to output stack, and '*' to operator stack
                 tok.toktype = TOK_CONST;
                 tok.datatype = 'i';
-                tok.i = 0;
+                tok.i = -1;
                 PUSH_TO_OUTPUT(tok);
                 tok.toktype = TOK_OP;
-                tok.op = OP_SUBTRACT;
+                tok.op = OP_MULTIPLY;
                 PUSH_TO_OPERATOR(tok);
                 allow_toktype = TOK_CONST | TOK_VAR | TOK_FUNC | TOK_VFUNC;
                 break;
+            case TOK_ASSIGNMENT:
+                // assignment to variable
+                // for now we assume variable is output (VAR_Y)
+                if (!assigning)
+                    {FAIL("Misplaced assignment operator.");}
+                if (opstack_index >= 0 || outstack_index < 0)
+                    {FAIL("Malformed expression left of assignment.");}
+
+                if (outstack[outstack_index].toktype == TOK_VAR) {
+                    int var = outstack[outstack_index].var;
+                    if (var == VAR_X)
+                        {FAIL("Cannot assign to input variable 'x'.");}
+                    else if (var == VAR_Y) {
+                        if (outstack[outstack_index].history_index == 0) {
+                            if (output_assigned)
+                                {FAIL("Variable 'y' already assigned.");}
+                            output_assigned = 1;
+                        }
+                    }
+                    else if (var >= N_VARS) {
+                        if (outstack[outstack_index].history_index == 0) {
+                            if (variables[var-N_VARS].assigned)
+                                {FAIL("Variable already assigned.");}
+                            variables[var-N_VARS].assigned = 1;
+                        }
+                    }
+                    // nothing extraordinary, continue as normal
+                    outstack[outstack_index].toktype = TOK_ASSIGNMENT;
+                    outstack[outstack_index].assignment_offset = 0;
+                    PUSH_TO_OPERATOR(outstack[outstack_index]);
+                    outstack_index--;
+                }
+                else if (outstack[outstack_index].toktype == TOK_VECTORIZE) {
+                    // outstack token is vectorizer
+                    outstack_index--;
+                    if (outstack[outstack_index].toktype != TOK_VAR)
+                        {FAIL("Illegal tokens left of assignment.");}
+                    int var = outstack[outstack_index].var;
+                    if (var == VAR_X)
+                        {FAIL("Cannot assign to input variable 'x'.");}
+                    else if (var == VAR_Y) {
+                        if (outstack[outstack_index].history_index == 0) {
+                            if (output_assigned)
+                                {FAIL("Variable 'y' already assigned.");}
+                            output_assigned = 1;
+                        }
+                    }
+                    else if (var >= N_VARS) {
+                        if (outstack[outstack_index].history_index == 0) {
+                            if (variables[var-N_VARS].assigned)
+                                {FAIL("Variable already assigned.");}
+                            variables[var-N_VARS].assigned = 1;
+                        }
+                    }
+                    while (outstack_index >= 0) {
+                        if (outstack[outstack_index].toktype != TOK_VAR)
+                            {FAIL("Illegal tokens left of assignment.");}
+                        else if (outstack[outstack_index].var != var)
+                            {FAIL("Cannot mix variables in vector assignment.");}
+                        outstack[outstack_index].toktype = TOK_ASSIGNMENT;
+                        PUSH_TO_OPERATOR(outstack[outstack_index]);
+                        outstack_index--;
+                    }
+                    int index = opstack_index, vector_count = 0;
+                    while (index >= 0) {
+                        opstack[index].assignment_offset = vector_count;
+                        vector_count += opstack[index].vector_length;
+                        index--;
+                    }
+                }
+                else
+                    {FAIL("Malformed expression left of assignment.");}
+                assigning = 0;
+                allow_toktype = 0xFFFF;
+                break;
             default:
                 {FAIL("Unknown token type.");}
                 break;
@@ -1436,41 +1882,62 @@ mapper_expr mapper_expr_new_from_string(const char *str,
 #endif
     }
 
+    if (allow_toktype & TOK_CONST || assigning || !output_assigned)
+        {FAIL("Expression has no output assignment.");}
+
+    // check that all used-defined variables were assigned
+    for (i = 0; i < num_variables; i++) {
+        if (!variables[i].assigned)
+            {FAIL("User-defined variable not assigned.");}
+    }
+
     // finish popping operators to output, check for unbalanced parentheses
-    while (opstack_index >= 0) {
+    while (opstack_index >= 0 && opstack[opstack_index].toktype != TOK_ASSIGNMENT) {
         if (opstack[opstack_index].toktype == TOK_OPEN_PAREN)
             {FAIL("Unmatched parentheses or misplaced comma.");}
         POP_OPERATOR_TO_OUTPUT();
     }
+    // pop assignment operator(s) to output
+    while (opstack_index >= 0) {
+        if (opstack[opstack_index].toktype != TOK_ASSIGNMENT)
+            {FAIL("Malformed expression (5).");}
+        PUSH_TO_OUTPUT(opstack[opstack_index]);
+        POP_OPERATOR();
+    }
+    // check vector length and type
+    if (check_assignment_types_and_lengths(outstack, outstack_index) == -1)
+        {FAIL("Malformed expression (6).");}
 
-    // Fail if top vector length doesn't match output vector length
-    if (outstack[outstack_index].vector_length != output_vector_size)
-        {FAIL("Expression vector length does not match destination.");}
+#if TRACING
+    printstack("--->OUTPUT STACK:", outstack, outstack_index);
+    printstack("--->OPERATOR STACK:", opstack, opstack_index);
+#endif
 
     // Check for maximum vector length used in stack
-    int i;
     for (i = 0; i < outstack_index; i++) {
         if (outstack[i].vector_length > max_vector)
             max_vector = outstack[i].vector_length;
     }
 
-    // If stack top type doesn't match output type, add cast
-    if (outstack[outstack_index].datatype != output_type) {
-        promote_token_datatype(&outstack[outstack_index], output_type);
-        outstack_index = check_types_and_lengths(outstack, outstack_index);
-        if (outstack[outstack_index].datatype != output_type)
-            outstack[outstack_index].casttype = output_type;
-    }
-
     mapper_expr expr = malloc(sizeof(struct _mapper_expr));
     expr->length = outstack_index + 1;
-    expr->start = malloc(sizeof(struct _token)*expr->length);
-    memcpy(expr->start, &outstack, sizeof(struct _token)*expr->length);
+
+    // copy tokens
+    expr->tokens = malloc(sizeof(struct _token)*expr->length);
+    memcpy(expr->tokens, &outstack, sizeof(struct _token)*expr->length);
+    expr->start = expr->tokens;
     expr->vector_size = max_vector;
-    expr->input_history_size = *input_history_size = -oldest_input+1;
-    expr->output_history_size = *output_history_size = -oldest_output+1;
+    expr->input_history_size = -oldest_input+1;
+    expr->output_history_size = -oldest_output+1;
     expr->constant_output = constant_output;
 
+    if (num_variables) {
+        // copy user-defined variables
+        expr->variables = malloc(sizeof(mapper_variable_t)*num_variables);
+        memcpy(expr->variables, variables, sizeof(mapper_variable_t)*num_variables);
+    }
+    expr->num_variables = num_variables;
+
     return expr;
 }
 
@@ -1484,6 +1951,27 @@ int mapper_expr_output_history_size(mapper_expr expr)
     return expr->output_history_size;
 }
 
+int mapper_expr_num_variables(mapper_expr expr)
+{
+    return expr->num_variables;
+}
+
+int mapper_expr_variable_history_size(mapper_expr expr, int index)
+{
+    if (index >= expr->num_variables)
+        return 0;
+    else
+        return expr->variables[index].history_size;
+}
+
+int mapper_expr_variable_vector_length(mapper_expr expr, int index)
+{
+    if (index >= expr->num_variables)
+        return 0;
+    else
+        return expr->variables[index].vector_length;
+}
+
 int mapper_expr_constant_output(mapper_expr expr)
 {
     if (expr->constant_output)
@@ -1523,19 +2011,36 @@ static void print_stack_vector(mapper_signal_value_t *stack, char type,
 
 int mapper_expr_evaluate(mapper_expr expr,
                          mapper_signal_history_t *from,
-                         mapper_signal_history_t *to)
+                         mapper_signal_history_t **expr_vars,
+                         mapper_signal_history_t *to,
+                         char *typestring)
 {
     mapper_signal_value_t stack[expr->length][expr->vector_size];
     int dims[expr->length];
 
-    int i, j, k, top = -1, count = 0;
+    int i, j, k, top = -1, count = 0, found, updated = 0;
     mapper_token_t *tok = expr->start;
 
+    // init typestring
+    if (typestring)
+        memset(typestring, 'N', to->length);
+
+    /* Increment index position of output data structure. */
+    to->position = (to->position + 1) % to->size;
+
     while (count < expr->length && tok->toktype != TOK_END) {
         switch (tok->toktype) {
         case TOK_CONST:
             ++top;
             dims[top] = tok->vector_length;
+#if TRACING
+            if (tok->datatype == 'f')
+                printf("storing const %f\n", tok->f);
+            else if (tok->datatype == 'i')
+                printf("storing const %i\n", tok->i);
+            else if (tok->datatype == 'd')
+                printf("storing const %f\n", tok->d);
+#endif
             if (tok->datatype == 'f') {
                 for (i = 0; i < tok->vector_length; i++)
                     stack[top][i].f = tok->f;
@@ -1577,7 +2082,7 @@ int mapper_expr_evaluate(mapper_expr expr,
                 case VAR_Y:
                     ++top;
                     dims[top] = tok->vector_length;
-                    idx = ((tok->history_index + to->position + 1
+                    idx = ((tok->history_index + to->position
                             + to->size) % to->size);
                     if (to->type == 'd') {
                         double *v = to->value + idx * to->length * mapper_type_size(to->type);
@@ -1595,7 +2100,19 @@ int mapper_expr_evaluate(mapper_expr expr,
                             stack[top][i].i32 = v[i+tok->vector_index];
                     }
                     break;
-                default: goto error;
+                default:
+                    // TODO: allow other data types?
+                    if (tok->var > expr->num_variables + N_VARS)
+                        goto error;
+                    ++top;
+                    dims[top] = tok->vector_length;
+                    mapper_variable var = &expr->variables[tok->var - N_VARS];
+                    mapper_signal_history_t *h = *expr_vars + (tok->var - N_VARS);
+                    idx = ((tok->history_index + h->position
+                            + var->history_size) % var->history_size);
+                    double *v = h->value + idx * var->vector_length * mapper_type_size(var->datatype);
+                    for (i = 0; i < tok->vector_length; i++)
+                        stack[top][i].d = v[i+tok->vector_index];
                 }
             }
             break;
@@ -1690,8 +2207,23 @@ int mapper_expr_evaluate(mapper_expr expr,
                         for (i = 0; i < tok->vector_length; i++) {
                             if (stack[top][i].f)
                                 stack[top][i].f = stack[top+1][i].f;
-                            else
-                                return 0;
+                            else {
+                                // skip ahead until after assignment
+                                found = 0;
+                                tok++;
+                                count++;
+                                while (count < expr->length && tok->toktype != TOK_END) {
+                                    if (tok->toktype == TOK_ASSIGNMENT)
+                                        found = 1;
+                                    else if (found) {
+                                        --tok;
+                                        --count;
+                                        break;
+                                    }
+                                    tok++;
+                                    count++;
+                                }
+                            }
                         }
                         break;
                     case OP_CONDITIONAL_IF_ELSE:
@@ -1772,8 +2304,23 @@ int mapper_expr_evaluate(mapper_expr expr,
                         for (i = 0; i < tok->vector_length; i++) {
                             if (stack[top][i].d)
                                 stack[top][i].d = stack[top+1][i].d;
-                            else
-                                return 0;
+                            else {
+                                // skip ahead until after assignment
+                                found = 0; // found assignment
+                                tok++;
+                                count++;
+                                while (count < expr->length && tok->toktype != TOK_END) {
+                                    if (tok->toktype == TOK_ASSIGNMENT)
+                                        found = 1;
+                                    else if (found) {
+                                        --tok;
+                                        --count;
+                                        break;
+                                    }
+                                    tok++;
+                                    count++;
+                                }
+                            }
                         }
                         break;
                     case OP_CONDITIONAL_IF_ELSE:
@@ -1874,8 +2421,23 @@ int mapper_expr_evaluate(mapper_expr expr,
                         for (i = 0; i < tok->vector_length; i++) {
                             if (stack[top][i].i32)
                                 stack[top][i].i32 = stack[top+1][i].i32;
-                            else
-                                return 0;
+                            else {
+                                // skip ahead until after assignment
+                                found = 0; // found assignment
+                                tok++;
+                                count++;
+                                while (count < expr->length && tok->toktype != TOK_END) {
+                                    if (tok->toktype == TOK_ASSIGNMENT)
+                                        found = 1;
+                                    else if (found) {
+                                        --tok;
+                                        --count;
+                                        break;
+                                    }
+                                    tok++;
+                                    count++;
+                                }
+                            }
                         }
                         break;
                     case OP_CONDITIONAL_IF_ELSE:
@@ -1910,7 +2472,7 @@ int mapper_expr_evaluate(mapper_expr expr,
                 print_stack_vector(stack[top], tok->datatype, tok->vector_length);
                 printf(", ");
             }
-            printf("\b\b)");
+            printf("%s)", function_table[tok->func].arity ? "\b\b" : "");
 #endif
             if (tok->datatype == 'f') {
                 switch (function_table[tok->func].arity) {
@@ -2018,22 +2580,22 @@ int mapper_expr_evaluate(mapper_expr expr,
             break;
         case TOK_VECTORIZE:
             // don't need to copy vector elements from first token
-            top -= tok->vector_index-1;
+            top -= tok->arity-1;
             k = dims[top];
             if (tok->datatype == 'f') {
-                for (i = 1; i < tok->vector_index; i++) {
+                for (i = 1; i < tok->arity; i++) {
                     for (j = 0; j < dims[top+1]; j++)
                         stack[top][k++].f = stack[top+i][j].f;
                 }
             }
             else if (tok->datatype == 'i') {
-                for (i = 1; i < tok->vector_index; i++) {
+                for (i = 1; i < tok->arity; i++) {
                     for (j = 0; j < dims[top+1]; j++)
                         stack[top][k++].i32 = stack[top+i][j].i32;
                 }
             }
             else if (tok->datatype == 'd') {
-                for (i = 1; i < tok->vector_index; i++) {
+                for (i = 1; i < tok->arity; i++) {
                     for (j = 0; j < dims[top+1]; j++)
                         stack[top][k++].d = stack[top+i][j].d;
                 }
@@ -2045,6 +2607,75 @@ int mapper_expr_evaluate(mapper_expr expr,
             printf(" \n");
 #endif
             break;
+        case TOK_ASSIGNMENT:
+#if TRACING
+            if (tok->var < N_VARS)
+                printf("assigning values to %s_%c%d{%i}[%i]\n",
+                       var_strings[tok->var], tok->datatype, tok->vector_length,
+                       tok->history_index, tok->vector_index);
+            else
+                printf("assigning values to var%d_%c%d{%i}[%i]\n",
+                       tok->var - N_VARS, tok->datatype, tok->vector_length,
+                       tok->history_index, tok->vector_index);
+#endif
+            updated++;
+            if (tok->var == VAR_Y) {
+                int idx = (tok->history_index + to->position + to->size);
+                if (idx < 0)
+                    idx = to->size - idx;
+                else
+                    idx %= to->size;
+
+                if (to->type == 'f') {
+                    float *v = to->value + idx * to->length * mapper_type_size(to->type);
+                    for (i = 0; i < tok->vector_length; i++)
+                        v[i + tok->vector_index] = stack[top][i + tok->assignment_offset].f;
+                }
+                else if (to->type == 'i') {
+                    int *v = to->value + idx * to->length * mapper_type_size(to->type);
+                    for (i = 0; i < tok->vector_length; i++)
+                        v[i + tok->vector_index] = stack[top][i + tok->assignment_offset].i32;
+                }
+                else if (to->type == 'd') {
+                    double *v = to->value + idx * to->length * mapper_type_size(to->type);
+                    for (i = 0; i < tok->vector_length; i++)
+                        v[i + tok->vector_index] = stack[top][i + tok->assignment_offset].d;
+                }
+
+                if (typestring) {
+                    for (i = tok->vector_index; i < tok->vector_index + tok->vector_length; i++) {
+                        typestring[i] = tok->datatype;
+                    }
+                }
+            }
+            else if (tok->var >= 0 && tok->var < expr->num_variables + N_VARS) {
+                // passed the address of an array of mapper_signal_history structs
+                mapper_signal_history_t *h = *expr_vars + (tok->var - N_VARS);
+                // increment position
+                h->position = (h->position + 1) % h->size;
+
+                mapper_variable var = &expr->variables[tok->var - N_VARS];
+                int idx = (tok->history_index + h->position
+                           + var->history_size) % var->history_size;
+                double *v = h->value + idx * var->vector_length * mapper_type_size(var->datatype);
+                for (i = 0; i < tok->vector_length; i++)
+                    v[i + tok->vector_index] = stack[top][i + tok->assignment_offset].d;
+
+                // Also copy timetag from input
+                mapper_timetag_t *ttfrom = msig_history_tt_pointer(*from);
+                mapper_timetag_t *ttvar = msig_history_tt_pointer(*h);
+                memcpy(ttvar, ttfrom, sizeof(mapper_timetag_t));
+            }
+
+            /* If assignment was history initialization, move expression start
+             * token pointer so we don't evaluate this section again. */
+            if (tok->history_index != 0) {
+                int offset = tok - expr->start + 1;
+                expr->start = tok+1;
+                expr->length -= offset;
+                count -= offset;
+            }
+            break;
         default: goto error;
         }
         if (tok->casttype) {
@@ -2093,27 +2724,39 @@ int mapper_expr_evaluate(mapper_expr expr,
         count++;
     }
 
-    /* Increment index position of output data structure.
-     * We do this after computation to handle conditional output. */
-    to->position = (to->position + 1) % to->size;
+    if (!typestring) {
+        /* Internal evaluation during parsing doesn't contain assignment token,
+         * so we need to copy to output here. */
 
-    if (to->type == 'f') {
-        float *v = msig_history_value_pointer(*to);
-        for (i = 0; i < to->length; i++)
-            v[i] = stack[top][i].f;
-    }
-    else if (to->type == 'i') {
-        int *v = msig_history_value_pointer(*to);
-        for (i = 0; i < to->length; i++)
-            v[i] = stack[top][i].i32;
-    }
-    else if (to->type == 'd') {
-        double *v = msig_history_value_pointer(*to);
-        for (i = 0; i < to->length; i++)
-            v[i] = stack[top][i].d;
+        /* Increment index position of output data structure. */
+        to->position = (to->position + 1) % to->size;
+
+        if (to->type == 'f') {
+            float *v = msig_history_value_pointer(*to);
+            for (i = 0; i < to->length; i++)
+                v[i] = stack[top][i].f;
+        }
+        else if (to->type == 'i') {
+            int *v = msig_history_value_pointer(*to);
+            for (i = 0; i < to->length; i++)
+                v[i] = stack[top][i].i32;
+        }
+        else if (to->type == 'd') {
+            double *v = msig_history_value_pointer(*to);
+            for (i = 0; i < to->length; i++)
+                v[i] = stack[top][i].d;
+        }
+        return 1;
     }
 
-    if (from) {
+    /* Undo position increment if nothing was updated. */
+    if (!updated) {
+        --to->position;
+        if (to->position < 0)
+            to->position = to->size - 1;
+        return 0;
+    }
+    else if (from) {
         // Also copy timetag from input
         mapper_timetag_t *ttfrom = msig_history_tt_pointer(*from);
         mapper_timetag_t *ttto = msig_history_tt_pointer(*to);
diff --git a/src/libmapper.def b/src/libmapper.def
index 61dd713..b55c515 100644
--- a/src/libmapper.def
+++ b/src/libmapper.def
@@ -7,8 +7,8 @@ EXPORTS
     mapper_db_add_signal_callback                             @6
     mapper_db_connection_done                                 @7
     mapper_db_connection_next                                 @8
-	mapper_db_connection_property_index                       @9
-	mapper_db_connection_property_lookup                      @10
+    mapper_db_connection_property_index                       @9
+    mapper_db_connection_property_lookup                      @10
     mapper_db_device_done                                     @11
     mapper_db_device_next                                     @12
     mapper_db_device_property_index                           @13
@@ -50,7 +50,7 @@ EXPORTS
     mapper_db_signal_next                                     @49
     mapper_db_signal_property_index                           @50
     mapper_db_signal_property_lookup                          @51
-    mapper_monitor_autorequest                                @52
+    mapper_monitor_autosubscribe                              @52
     mapper_monitor_connect                                    @53
     mapper_monitor_connection_modify                          @54
     mapper_monitor_disconnect                                 @55
@@ -59,51 +59,49 @@ EXPORTS
     mapper_monitor_link                                       @58
     mapper_monitor_new                                        @59
     mapper_monitor_poll                                       @60
-    mapper_monitor_request_connections_by_name                @61
-    mapper_monitor_request_devices                            @62
-    mapper_monitor_request_links_by_name                      @63
-    mapper_monitor_request_signals_by_name                    @64
-    mapper_monitor_unlink                                     @65
-    mdev_add_input                                            @66
-    mdev_add_output                                           @67
-    mdev_free                                                 @68
-    mdev_get_input_by_index                                   @69
-    mdev_get_input_by_name                                    @70
-    mdev_get_inputs                                           @71
-    mdev_get_lo_server                                        @72
-    mdev_get_output_by_index                                  @73
-    mdev_get_output_by_name                                   @74
-    mdev_get_outputs                                          @75
-    mdev_interface                                            @76
-    mdev_ip4                                                  @77
-    mdev_name                                                 @78
-    mdev_new                                                  @79
-    mdev_num_inputs                                           @80
-    mdev_num_outputs                                          @81
-    mdev_ordinal                                              @82
-    mdev_poll                                                 @83
-    mdev_port                                                 @84
-    mdev_ready                                                @85
-    mdev_remove_input                                         @86
-    mdev_remove_output                                        @87
-    mdev_remove_property                                      @88
-    mdev_set_property                                         @89
-    msig_active_instance_id                                   @90
-    msig_full_name                                            @91
-    msig_num_active_instances                                 @92
-    msig_num_reserved_instances                               @93
-    msig_properties                                           @94
-    msig_query_remotes                                        @95
-    msig_release_instance                                     @96
-    msig_remove_property                                      @97
-    msig_reserve_instances                                    @98
-    msig_set_instance_allocation_mode                         @99
-    msig_set_instance_event_callback                          @100
-    msig_set_maximum                                          @101
-    msig_set_minimum                                          @102
-    msig_set_property                                         @103
-    msig_update                                               @104
-    msig_update_float                                         @105
-    msig_update_instance                                      @106
-    msig_update_int                                           @107
-    msig_value                                                @108
+    mapper_monitor_subscribe                                  @61
+    mapper_monitor_unlink                                     @62
+    mapper_monitor_unsubscribe                                @63
+    mdev_add_input                                            @64
+    mdev_add_output                                           @65
+    mdev_free                                                 @66
+    mdev_get_input_by_index                                   @67
+    mdev_get_input_by_name                                    @68
+    mdev_get_inputs                                           @69
+    mdev_get_lo_server                                        @70
+    mdev_get_output_by_index                                  @71
+    mdev_get_output_by_name                                   @72
+    mdev_get_outputs                                          @73
+    mdev_interface                                            @74
+    mdev_ip4                                                  @75
+    mdev_name                                                 @76
+    mdev_new                                                  @77
+    mdev_num_inputs                                           @78
+    mdev_num_outputs                                          @79
+    mdev_ordinal                                              @80
+    mdev_poll                                                 @81
+    mdev_port                                                 @82
+    mdev_ready                                                @83
+    mdev_remove_input                                         @84
+    mdev_remove_output                                        @85
+    mdev_remove_property                                      @86
+    mdev_set_property                                         @87
+    msig_active_instance_id                                   @88
+    msig_full_name                                            @89
+    msig_num_active_instances                                 @90
+    msig_num_reserved_instances                               @91
+    msig_properties                                           @92
+    msig_query_remotes                                        @93
+    msig_release_instance                                     @94
+    msig_remove_property                                      @95
+    msig_reserve_instances                                    @96
+    msig_set_instance_allocation_mode                         @97
+    msig_set_instance_event_callback                          @98
+    msig_set_maximum                                          @99
+    msig_set_minimum                                          @100
+    msig_set_property                                         @101
+    msig_update                                               @102
+    msig_update_float                                         @103
+    msig_update_instance                                      @104
+    msig_update_int                                           @105
+    msig_value                                                @106
diff --git a/src/mapper_internal.h b/src/mapper_internal.h
index 641fc94..dde4ea1 100644
--- a/src/mapper_internal.h
+++ b/src/mapper_internal.h
@@ -29,6 +29,9 @@ struct _mapper_signal
     /*! Array of pointers to the signal instances. */
     struct _mapper_signal_instance **instances;
 
+    /*! Bitflag value when entire signal vector is known. */
+    char *has_complete_value;
+
     /*! Type of voice stealing to perform on instances. */
     mapper_instance_allocation_type instance_allocation_type;
 
@@ -85,6 +88,8 @@ struct _mapper_device {
     struct _mapper_id_map *reserve_id_map;
 
     uint32_t id_counter;
+    int link_timeout_sec;   /* Number of seconds after which unresponsive
+                             * links will be removed, or 0 for never. */
 
     /*! Server used to handle incoming messages. */
     lo_server server;
@@ -113,6 +118,7 @@ typedef struct _mapper_signal_instance
 
     /*! Indicates whether this instance has a value. */
     int has_value;
+    char *has_value_flags;
 
     /*! The current value of this signal instance. */
     void *value;
@@ -150,28 +156,40 @@ void mapper_admin_remove_monitor(mapper_admin admin, mapper_monitor mon);
 
 int mapper_admin_poll(mapper_admin admin);
 
+void mapper_admin_set_bundle_dest_bus(mapper_admin admin);
+
+void mapper_admin_set_bundle_dest_mesh(mapper_admin admin, lo_address address);
+
+void mapper_admin_set_bundle_dest_subscribers(mapper_admin admin, int type);
+
 void mapper_admin_send_bundle(mapper_admin admin);
 
+void mapper_admin_send_signal(mapper_admin admin, mapper_device md,
+                              mapper_signal sig);
+
+void mapper_admin_send_signal_removed(mapper_admin admin, mapper_device md,
+                                      mapper_signal sig);
+
 /*! Macro for calling message-sending function. */
-#define mapper_admin_send(...)                  \
-    _real_mapper_admin_send(__VA_ARGS__, N_AT_PARAMS)
+#define mapper_admin_bundle_message(...)                                    \
+    _real_mapper_admin_bundle_message(__VA_ARGS__, N_AT_PARAMS)
 
 /*! Message-sending function, not to be called directly. */
-void _real_mapper_admin_send(mapper_admin admin,
-                             int msg_index,
-                             const char *path,
-                             const char *types, ...);
+void _real_mapper_admin_bundle_message(mapper_admin admin,
+                                       int msg_index,
+                                       const char *path,
+                                       const char *types, ...);
 
 /*! Message-sending function which appends a parameter list at the end. */
-void _real_mapper_admin_send_with_params(mapper_admin admin,
-                                         mapper_message_t *params,
-                                         mapper_string_table_t *extra,
-                                         int msg_index,
-                                         const char *path,
-                                         const char *types, ...);
+void _real_mapper_admin_bundle_message_with_params(mapper_admin admin,
+                                                   mapper_message_t *params,
+                                                   mapper_string_table_t *extra,
+                                                   int msg_index,
+                                                   const char *path,
+                                                   const char *types, ...);
 
-#define mapper_admin_send_with_params(...)                          \
-    _real_mapper_admin_send_with_params(__VA_ARGS__, N_AT_PARAMS)
+#define mapper_admin_bundle_message_with_params(...)                        \
+    _real_mapper_admin_bundle_message_with_params(__VA_ARGS__, N_AT_PARAMS)
 
 /***** Device *****/
 
@@ -240,7 +258,8 @@ const char *mdev_name(mapper_device md);
 /***** Router *****/
 
 mapper_router mapper_router_new(mapper_device device, const char *host,
-                                int port, const char *name);
+                                int admin_port, int data_port,
+                                const char *name);
 
 void mapper_router_free(mapper_router router);
 
@@ -261,12 +280,11 @@ void mapper_router_process_signal(mapper_router r,
                                   int count,
                                   mapper_timetag_t timetag);
 
-void mapper_router_send_update(mapper_router r,
-                               mapper_connection c,
-                               int history_index,
-                               mapper_id_map id_map,
-                               mapper_timetag_t tt,
-                               lo_blob blob);
+lo_message mapper_router_build_message(void *value,
+                                       int length,
+                                       char type,
+                                       char *typestring,
+                                       mapper_id_map id_map);
 
 int mapper_router_send_query(mapper_router router,
                              mapper_signal sig,
@@ -297,6 +315,10 @@ mapper_router mapper_router_find_by_dest_address(mapper_router routers,
 mapper_router mapper_router_find_by_dest_name(mapper_router routers,
                                               const char *dest_name);
 
+/*! Find a router by destination device hash in a linked list of routers. */
+mapper_router mapper_router_find_by_dest_hash(mapper_router routers,
+                                              uint32_t hash);
+
 void mapper_router_start_queue(mapper_router router, mapper_timetag_t tt);
 
 void mapper_router_send_queue(mapper_router router, mapper_timetag_t tt);
@@ -304,7 +326,8 @@ void mapper_router_send_queue(mapper_router router, mapper_timetag_t tt);
 /***** Receiver *****/
 
 mapper_receiver mapper_receiver_new(mapper_device device, const char *host,
-                                    int port, const char *name);
+                                    int admin_port, int data_port,
+                                    const char *name);
 
 void mapper_receiver_free(mapper_receiver receiver);
 
@@ -347,6 +370,10 @@ mapper_receiver mapper_receiver_find_by_src_address(mapper_receiver receivers,
 mapper_receiver mapper_receiver_find_by_src_name(mapper_receiver receivers,
                                                  const char *src_name);
 
+/*! Find a receiver by source device hash in a linked list of receivers. */
+mapper_receiver mapper_receiver_find_by_src_hash(mapper_receiver receivers,
+                                                 uint32_t hash);
+
 int mapper_receiver_add_scope(mapper_receiver receiver, const char *scope);
 
 int mapper_receiver_remove_scope(mapper_receiver receiver, const char *scope);
@@ -366,12 +393,13 @@ int mapper_receiver_remove_scope(mapper_receiver receiver, const char *scope);
  *  \param unit The unit of the signal, or 0 for none.
  *  \param minimum Pointer to a minimum value, or 0 for none.
  *  \param maximum Pointer to a maximum value, or 0 for none.
+ *  \param number of signal instances.
  *  \param handler Function to be called when the value of the
  *                 signal is updated.
  *  \param user_data User context pointer to be passed to handler. */
 mapper_signal msig_new(const char *name, int length, char type,
                        int is_output, const char *unit,
-                       void *minimum, void *maximum,
+                       void *minimum, void *maximum, int num_instances,
                        mapper_signal_update_handler *handler,
                        void *user_data);
 
@@ -381,6 +409,14 @@ mapper_signal msig_new(const char *name, int length, char type,
  *  \param sig The signal to free. */
 void msig_free(mapper_signal sig);
 
+/*! Coerce a signal instance value to a particular type and vector length
+ *  and add it to a lo_message. */
+void message_add_coerced_signal_instance_value(lo_message m,
+                                               mapper_signal sig,
+                                               mapper_signal_instance si,
+                                               int length,
+                                               const char type);
+
 /**** Instances ****/
 
 /*! Store an instance id_map record.
@@ -451,7 +487,9 @@ void msig_release_instance_internal(mapper_signal sig,
  *  \return Zero if the operation was muted, or one if it was performed. */
 int mapper_connection_perform(mapper_connection connection,
                               mapper_signal_history_t *from_value,
-                              mapper_signal_history_t *to_value);
+                              mapper_signal_history_t **expr_vars,
+                              mapper_signal_history_t *to_value,
+                              char *typestring);
 
 int mapper_boundary_perform(mapper_connection connection,
                             mapper_signal_history_t *from_value);
@@ -477,15 +515,17 @@ const char *mapper_get_mode_type_string(mapper_mode_type mode);
 
 /*! Add or update an entry in the device database using parsed message
  *  parameters.
- *  \param db          The database to operate on.
- *  \param device_name The name of the device.
- *  \param params      The parsed message parameters containing new device
- *                     information.
- *  \return            Non-zero if device was added to the database, or
- *                     zero if it was already present. */
+ *  \param db           The database to operate on.
+ *  \param device_name  The name of the device.
+ *  \param params       The parsed message parameters containing new device
+ *                      information.
+ *  \param current_time The current time.
+ *  \return             Non-zero if device was added to the database, or
+ *                      zero if it was already present. */
 int mapper_db_add_or_update_device_params(mapper_db db,
                                           const char *device_name,
-                                          mapper_message_t *params);
+                                          mapper_message_t *params,
+                                          mapper_timetag_t *current_time);
 
 /*! Add or update an entry in the signal database using parsed message
  *  parameters.
@@ -523,6 +563,14 @@ int mapper_db_add_or_update_connection_params(mapper_db db,
 /*! Remove a named device from the database if it exists. */
 void mapper_db_remove_device_by_name(mapper_db db, const char *name);
 
+/*! Remove a named input signal from the database if it exists. */
+void mapper_db_remove_input_by_name(mapper_db db, const char *dev_name,
+                                    const char *sig_name);
+
+/*! Remove a named output signal from the database if it exists. */
+void mapper_db_remove_output_by_name(mapper_db db, const char *dev_name,
+                                    const char *sig_name);
+
 /*! Remove signals in the provided query. */
 void mapper_db_remove_inputs_by_query(mapper_db db,
                                       mapper_db_signal_t **s);
@@ -553,6 +601,13 @@ void mapper_db_dump(mapper_db db);
 
 void mapper_db_remove_all_callbacks(mapper_db db);
 
+/*! Check device records for unresponsive devices. */
+void mapper_db_check_device_status(mapper_db db, uint32_t now_sec);
+
+/*! Flush device records for unresponsive devices. */
+int mapper_db_flush(mapper_db db, uint32_t current_time,
+                    uint32_t timeout, int quiet);
+
 /**** Links ****/
 
 /*! Add or update an entry in the link database using parsed message
@@ -577,6 +632,11 @@ int mapper_db_link_add_scope(mapper_db_link link,
 int mapper_db_link_remove_scope(mapper_db_link link,
                                 const char *scope);
 
+/**** Connections ****/
+
+void mhist_realloc(mapper_signal_history_t *history, int history_size,
+                   int sample_size, int is_output);
+
 /**** Messages ****/
 
 /*! Parse a message based on an OSC path and parameters.
@@ -706,21 +766,27 @@ mapper_expr mapper_expr_new_from_string(const char *str,
                                         char input_type,
                                         char output_type,
                                         int input_vector_size,
-                                        int output_vector_size,
-                                        int *input_history_size,
-                                        int *output_history_size);
+                                        int output_vector_size);
 
 int mapper_expr_input_history_size(mapper_expr expr);
 
 int mapper_expr_output_history_size(mapper_expr expr);
 
+int mapper_expr_num_variables(mapper_expr expr);
+
+int mapper_expr_variable_history_size(mapper_expr expr, int index);
+
+int mapper_expr_variable_vector_length(mapper_expr expr, int index);
+
 #ifdef DEBUG
 void printexpr(const char*, mapper_expr);
 #endif
 
 int mapper_expr_evaluate(mapper_expr expr,
-                         mapper_signal_history_t *input_history,
-                         mapper_signal_history_t *output_history);
+                         mapper_signal_history_t *from_value,
+                         mapper_signal_history_t **expr_vars,
+                         mapper_signal_history_t *to_value,
+                         char *typestring);
 
 int mapper_expr_constant_output(mapper_expr expr);
 
@@ -805,11 +871,6 @@ void mapper_msg_add_value_table(lo_message m, table t);
 /*! Initialize a mapper_clock. */
 void mapper_clock_init(mapper_clock clock);
 
-/*! Adjust the internal clock synchonization. */
-void mapper_clock_adjust(mapper_clock clock,
-                         double difference,
-                         float confidence);
-
 /*! Get the current time from a mapper_clock. */
 void mapper_clock_now(mapper_clock clock, mapper_timetag_t *timetag);
 
@@ -859,7 +920,7 @@ inline static int mapper_type_size(char type)
 }
 
 /*! Helper to find the size in bytes of a signal's full vector. */
-inline static int msig_vector_bytes(mapper_signal sig)
+inline static size_t msig_vector_bytes(mapper_signal sig)
 {
     return mapper_type_size(sig->props.type) * sig->props.length;
 }
diff --git a/src/monitor.c b/src/monitor.c
index 14b38b0..49c2e59 100644
--- a/src/monitor.c
+++ b/src/monitor.c
@@ -8,6 +8,8 @@
 #include "config.h"
 #include "mapper_internal.h"
 
+#define AUTOSUBSCRIBE_INTERVAL 60
+
 /*! Internal function to get the current time. */
 static double get_current_time()
 {
@@ -20,14 +22,18 @@ static double get_current_time()
 #endif
 }
 
+// function prototypes
+static void monitor_subscribe_internal(mapper_monitor mon, const char *device_name,
+                                       int subscribe_flags, int timeout,
+                                       int version);
+
 typedef enum _db_request_direction {
     DIRECTION_IN,
     DIRECTION_OUT,
     DIRECTION_BOTH
 } db_request_direction;
 
-mapper_monitor mapper_monitor_new(mapper_admin admin,
-                                  mapper_monitor_autoreq_mode_t flags)
+mapper_monitor mapper_monitor_new(mapper_admin admin, int autosubscribe_flags)
 {
     mapper_monitor mon = (mapper_monitor)
         calloc(1, sizeof(struct _mapper_monitor));
@@ -46,9 +52,13 @@ mapper_monitor mapper_monitor_new(mapper_admin admin,
         return NULL;
     }
 
+    mon->timeout_sec = ADMIN_TIMEOUT_SEC;
+
     mapper_admin_add_monitor(mon->admin, mon);
-    if (flags)
-        mapper_monitor_autorequest(mon, flags);
+    if (autosubscribe_flags) {
+        mapper_monitor_autosubscribe(mon, autosubscribe_flags);
+        mapper_monitor_request_devices(mon);
+    }
     return mon;
 }
 
@@ -60,6 +70,11 @@ void mapper_monitor_free(mapper_monitor mon)
     // remove callbacks now so they won't be called when removing devices
     mapper_db_remove_all_callbacks(&mon->db);
 
+    // unsubscribe from and remove any autorenewing subscriptions
+    while (mon->subscriptions) {
+        mapper_monitor_unsubscribe(mon, mon->subscriptions->name);
+    }
+
     while (mon->db.registered_devices)
         mapper_db_remove_device_by_name(&mon->db, mon->db.registered_devices->name);
     if (mon->admin) {
@@ -73,7 +88,23 @@ void mapper_monitor_free(mapper_monitor mon)
 
 int mapper_monitor_poll(mapper_monitor mon, int block_ms)
 {
+    int ping_time = mon->admin->clock.next_ping;
     int admin_count = mapper_admin_poll(mon->admin);
+    mapper_clock_now(&mon->admin->clock, &mon->admin->clock.now);
+
+    // check if any subscriptions need to be renewed
+    mapper_monitor_subscription s = mon->subscriptions;
+    while (s) {
+        if (s->lease_expiration_sec < mon->admin->clock.now.sec) {
+            monitor_subscribe_internal(mon, s->name, s->flags,
+                                       AUTOSUBSCRIBE_INTERVAL, -1);
+            // leave 10-second buffer for subscription renewal
+            s->lease_expiration_sec =
+                mon->admin->clock.now.sec + AUTOSUBSCRIBE_INTERVAL - 10;
+        }
+        s = s->next;
+    }
+
     if (block_ms) {
         double then = get_current_time();
         while ((get_current_time() - then)*1000 < block_ms) {
@@ -85,6 +116,13 @@ int mapper_monitor_poll(mapper_monitor mon, int block_ms)
 #endif
         }
     }
+
+    if (ping_time != mon->admin->clock.next_ping) {
+        // some housekeeping: check if any devices have timed out
+        mapper_db_check_device_status(&mon->db,
+                                      mon->admin->clock.now.sec - mon->timeout_sec);
+    }
+
     return admin_count;
 }
 
@@ -93,431 +131,140 @@ mapper_db mapper_monitor_get_db(mapper_monitor mon)
     return &mon->db;
 }
 
-static int request_signals_by_device_name_internal(mapper_monitor mon,
-                                                   const char* name,
-                                                   int direction)
-{
-	char cmd[1024];
-    if (direction == DIRECTION_IN)
-        snprintf(cmd, 1024, "%s/signals/input/get", name);
-    else if (direction == DIRECTION_OUT)
-        snprintf(cmd, 1024, "%s/signals/output/get", name);
-    else
-        snprintf(cmd, 1024, "%s/signals/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-int mapper_monitor_request_signals_by_device_name(mapper_monitor mon,
-                                                  const char* name)
-{
-    return request_signals_by_device_name_internal(mon, name, DIRECTION_BOTH);
-}
-
-int mapper_monitor_request_input_signals_by_device_name(mapper_monitor mon,
-                                                        const char* name)
+static void mapper_monitor_set_bundle_dest(mapper_monitor mon, const char *name)
 {
-    return request_signals_by_device_name_internal(mon, name, DIRECTION_IN);
+    // TODO: look up device info, maybe send directly
+    mapper_admin_set_bundle_dest_bus(mon->admin);
 }
 
-int mapper_monitor_request_output_signals_by_device_name(mapper_monitor mon,
-                                                         const char* name)
+static void monitor_subscribe_internal(mapper_monitor mon, const char *device_name,
+                                       int subscribe_flags, int timeout,
+                                       int version)
 {
-    return request_signals_by_device_name_internal(mon, name, DIRECTION_OUT);
-}
-
-static int request_signal_range_by_device_name_internal(mapper_monitor mon,
-                                                        const char* name,
-                                                        int start_index,
-                                                        int stop_index,
-                                                        int direction)
-{
-	char cmd[1024];
-    if (direction == DIRECTION_IN)
-        snprintf(cmd, 1024, "%s/signals/input/get", name);
-    else if (direction == DIRECTION_OUT)
-        snprintf(cmd, 1024, "%s/signals/output/get", name);
-    else
-        snprintf(cmd, 1024, "%s/signals/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "ii", start_index, stop_index);
-    return 0;
-}
-
-int mapper_monitor_request_signal_range_by_device_name(mapper_monitor mon,
-                                                       const char* name,
-                                                       int start_index,
-                                                       int stop_index)
-{
-    return request_signal_range_by_device_name_internal(mon, name,
-                                                        start_index,
-                                                        stop_index,
-                                                        DIRECTION_BOTH);
-}
-
-int mapper_monitor_request_input_signal_range_by_device_name(mapper_monitor mon,
-                                                             const char* name,
-                                                             int start_index,
-                                                             int stop_index)
-{
-    return request_signal_range_by_device_name_internal(mon, name,
-                                                        start_index,
-                                                        stop_index,
-                                                        DIRECTION_IN);
-}
-
-int mapper_monitor_request_output_signal_range_by_device_name(mapper_monitor mon,
-                                                              const char* name,
-                                                              int start_index,
-                                                              int stop_index)
-{
-	return request_signal_range_by_device_name_internal(mon, name,
-                                                        start_index,
-                                                        stop_index,
-                                                        DIRECTION_OUT);
-}
-
-static void on_signal_continue_batch_request(mapper_db_signal sig,
-                                             mapper_db_action_t a,
-                                             void *user)
-{
-    if (a != MDB_NEW)
-        return;
-
-    mapper_db_batch_request data = (mapper_db_batch_request)user;
-    if (!data)
-        return;
-
-    mapper_db_device dev_to_match = data->device;
-    if (strcmp(sig->device_name, dev_to_match->name) != 0)
-        return;
-    if (sig->id >= (data->total_count - 1)) {
-        // signal reporting is complete
-        mapper_db_remove_signal_callback(&data->monitor->db,
-                                         on_signal_continue_batch_request, data);
-        free(data);
-        return;
+    char cmd[1024];
+    snprintf(cmd, 1024, "%s/subscribe", device_name);
+
+    mapper_monitor_set_bundle_dest(mon, device_name);
+    lo_message m = lo_message_new();
+    if (m) {
+        if (subscribe_flags & SUB_DEVICE_ALL)
+            lo_message_add_string(m, "all");
+        else {
+            if (subscribe_flags & SUB_DEVICE)
+                lo_message_add_string(m, "device");
+            if (subscribe_flags & SUB_DEVICE_SIGNALS)
+                lo_message_add_string(m, "signals");
+            else {
+                if (subscribe_flags & SUB_DEVICE_INPUTS)
+                    lo_message_add_string(m, "inputs");
+                else if (subscribe_flags & SUB_DEVICE_OUTPUTS)
+                    lo_message_add_string(m, "outputs");
+            }
+            if (subscribe_flags & SUB_DEVICE_LINKS)
+                lo_message_add_string(m, "links");
+            else {
+                if (subscribe_flags & SUB_DEVICE_LINKS_IN)
+                    lo_message_add_string(m, "links_in");
+                else if (subscribe_flags & SUB_DEVICE_LINKS_OUT)
+                    lo_message_add_string(m, "links_out");
+            }
+            if (subscribe_flags & SUB_DEVICE_CONNECTIONS)
+                lo_message_add_string(m, "connections");
+            else {
+                if (subscribe_flags & SUB_DEVICE_CONNECTIONS_IN)
+                    lo_message_add_string(m, "connections_in");
+                else if (subscribe_flags & SUB_DEVICE_CONNECTIONS_OUT)
+                    lo_message_add_string(m, "connections_out");
+            }
+        }
+        lo_message_add_string(m, "@lease");
+        lo_message_add_int32(m, timeout);
+        if (version >= 0) {
+            lo_message_add_string(m, "@version");
+            lo_message_add_int32(m, version);
+        }
+        lo_bundle_add_message(mon->admin->bundle, cmd, m);
+        mapper_admin_send_bundle(mon->admin);
     }
-    if (sig->id > 0 && (sig->id % data->batch_size == 0))
-        request_signal_range_by_device_name_internal(data->monitor,
-                                                     data->device->name,
-                                                     sig->id + 1,
-                                                     sig->id + data->batch_size,
-                                                     data->direction);
 }
 
-static int batch_request_signals_by_device_name_internal(mapper_monitor mon,
-                                                         const char* name,
-                                                         int batch_size,
-                                                         int direction)
-{
-    // find the db record of device
-    mapper_db_device dev = mapper_db_get_device_by_name(&mon->db, name);
-    if (!dev) {
-        return 1;
-    }
-
-    int signal_count = 0;
-    char type;
-    const void *value;
-    int length;
-
-    if (!mapper_db_device_property_lookup(dev, "n_inputs", &type, &value, &length)) {
-        if (length && type == LO_INT32) {
-            int *vals = (int*)value;
-            signal_count = vals[0];
+void mapper_monitor_subscribe(mapper_monitor mon, const char *device_name,
+                              int subscribe_flags, int timeout)
+{
+    mapper_db_device found = 0;
+    if (timeout == -1) {
+        // special case: autorenew subscription lease
+        // first check if subscription already exists
+        mapper_monitor_subscription s = mon->subscriptions;
+        while (s) {
+            if (strcmp(device_name, s->name)==0) {
+                s->flags = subscribe_flags;
+                // subscription already exists; check if monitor has device record
+                if ((found = mapper_db_get_device_by_name(&mon->db, device_name)))
+                    return;
+                break;
+            }
+            s = s->next;
         }
-    }
-    if (!mapper_db_device_property_lookup(dev, "n_outputs", &type, &value, &length))
-        if (length && type == LO_INT32) {
-            int *vals = (int*)value;
-            signal_count = signal_count > vals[0] ? signal_count : vals[0];
+        if (!s) {
+            // store subscription record
+            s = malloc(sizeof(struct _mapper_monitor_subscription));
+            s->name = strdup(device_name);
+            s->flags = subscribe_flags;
+            s->next = mon->subscriptions;
+            mon->subscriptions = s;
         }
 
-    if (!signal_count)
-        return 1;
+        mapper_clock_now(&mon->admin->clock, &mon->admin->clock.now);
+        // leave 10-second buffer for subscription lease
+        s->lease_expiration_sec =
+            mon->admin->clock.now.sec + AUTOSUBSCRIBE_INTERVAL - 10;
 
-    if (signal_count <= batch_size) {
-        request_signals_by_device_name_internal(mon, name, direction);
-        return 1;
+        timeout = AUTOSUBSCRIBE_INTERVAL;
     }
 
-    mapper_db_batch_request data = (mapper_db_batch_request)
-    malloc(sizeof(struct _mapper_db_batch_request));
-    data->monitor = mon;
-    data->device = dev;
-    data->index = 0;
-    data->total_count = signal_count;
-    data->batch_size = batch_size;
-    data->direction = direction;
-
-    mapper_db_add_signal_callback(&mon->db, on_signal_continue_batch_request, data);
-    request_signal_range_by_device_name_internal(mon, name, 0, batch_size, direction);
-    return 0;
-}
-
-int mapper_monitor_batch_request_signals_by_device_name(mapper_monitor mon,
-                                                        const char* name,
-                                                        int batch_size)
-{
-    return batch_request_signals_by_device_name_internal(mon, name,
-                                                         batch_size,
-                                                         DIRECTION_BOTH);
+    monitor_subscribe_internal(mon, device_name, subscribe_flags, timeout, 0);
 }
 
-int mapper_monitor_batch_request_input_signals_by_device_name(mapper_monitor mon,
-                                                              const char* name,
-                                                              int batch_size)
-{
-    return batch_request_signals_by_device_name_internal(mon, name,
-                                                         batch_size,
-                                                         DIRECTION_IN);
-}
-
-int mapper_monitor_batch_request_output_signals_by_device_name(mapper_monitor mon,
-                                                               const char* name,
-                                                               int batch_size)
-{
-    return batch_request_signals_by_device_name_internal(mon, name,
-                                                         batch_size,
-                                                         DIRECTION_OUT);
-}
-
-int mapper_monitor_request_devices(mapper_monitor mon)
-{
-    mapper_admin_send(mon->admin, ADM_WHO, 0, "");
-    return 0;
-}
-
-int mapper_monitor_request_device_info(mapper_monitor mon,
-                                       const char* name)
+static void mapper_monitor_unsubscribe_internal(mapper_monitor mon,
+                                                const char *device_name,
+                                                int send_message)
 {
     char cmd[1024];
-	snprintf(cmd, 1024, "%s/info/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-int mapper_monitor_request_links_by_device_name(mapper_monitor mon,
-                                                const char* name)
-{
-	char cmd[1024];
-	snprintf(cmd, 1024, "%s/links/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-int mapper_monitor_request_links_by_src_device_name(mapper_monitor mon,
-                                                    const char* name)
-{
-	char cmd[1024];
-	snprintf(cmd, 1024, "%s/links/out/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-int mapper_monitor_request_links_by_dest_device_name(mapper_monitor mon,
-                                                    const char* name)
-{
-	char cmd[1024];
-	snprintf(cmd, 1024, "%s/links/in/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-static int request_connections_by_device_name_internal(mapper_monitor mon,
-                                                       const char* name,
-                                                       int direction)
-{
-	char cmd[1024];
-    if (direction == DIRECTION_IN)
-        snprintf(cmd, 1024, "%s/connections/in/get", name);
-    else if (direction == DIRECTION_OUT)
-        snprintf(cmd, 1024, "%s/connections/out/get", name);
-    else
-        snprintf(cmd, 1024, "%s/connections/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "");
-    return 0;
-}
-
-int mapper_monitor_request_connections_by_device_name(mapper_monitor mon,
-                                                      const char* name)
-{
-    return request_connections_by_device_name_internal(mon, name,
-                                                       DIRECTION_BOTH);
-}
-
-int mapper_monitor_request_connections_by_src_device_name(mapper_monitor mon,
-                                                          const char* name)
-{
-    return request_connections_by_device_name_internal(mon, name,
-                                                       DIRECTION_OUT);
-}
-
-int mapper_monitor_request_connections_by_dest_device_name(mapper_monitor mon,
-                                                           const char* name)
-{
-    return request_connections_by_device_name_internal(mon, name,
-                                                       DIRECTION_IN);
-}
-
-static int request_connection_range_by_device_name_internal(mapper_monitor mon,
-                                                            const char* name,
-                                                            int start_index,
-                                                            int stop_index,
-                                                            int direction)
-{
-	char cmd[1024];
-    if (direction == DIRECTION_IN)
-        snprintf(cmd, 1024, "%s/connections/in/get", name);
-    else if (direction == DIRECTION_OUT)
-        snprintf(cmd, 1024, "%s/connections/out/get", name);
-    else
-        snprintf(cmd, 1024, "%s/connections/get", name);
-	mapper_admin_send(mon->admin, -1, cmd, "ii", start_index, stop_index);
-    return 0;
-}
-
-int mapper_monitor_request_connection_range_by_device_name(mapper_monitor mon,
-                                                           const char* name,
-                                                           int start_index,
-                                                           int stop_index)
-{
-    return request_connection_range_by_device_name_internal(mon, name,
-                                                            start_index,
-                                                            stop_index,
-                                                            DIRECTION_BOTH);
-}
-
-int mapper_monitor_request_connection_range_by_src_device_name(mapper_monitor mon,
-                                                               const char* name,
-                                                               int start_index,
-                                                               int stop_index)
-{
-    return request_connection_range_by_device_name_internal(mon, name,
-                                                            start_index,
-                                                            stop_index,
-                                                            DIRECTION_OUT);
-}
-
-int mapper_monitor_request_connection_range_by_dest_device_name(mapper_monitor mon,
-                                                                const char* name,
-                                                                int start_index,
-                                                                int stop_index)
-{
-	return request_connection_range_by_device_name_internal(mon, name,
-                                                            start_index,
-                                                            stop_index,
-                                                            DIRECTION_IN);
-}
-
-static void on_connection_continue_batch_request(mapper_db_connection con,
-                                                 mapper_db_action_t a,
-                                                 void *user)
-{
-    if (a != MDB_NEW)
-        return;
-
-    mapper_db_batch_request data = (mapper_db_batch_request)user;
-    if (!data)
-        return;
-
-    mapper_db_device dev_to_match = data->device;
-    if (strcmp(con->src_name, dev_to_match->name) != 0)
-        return;
-    if (con->id >= (data->total_count - 1)) {
-        // connection reporting is complete
-        mapper_db_remove_connection_callback(&data->monitor->db,
-                                             on_connection_continue_batch_request,
-                                             data);
-        free(data);
-        return;
-    }
-    if (con->id > 0 && (con->id % data->batch_size == 0))
-        request_connection_range_by_device_name_internal(data->monitor,
-                                                         data->device->name,
-                                                         con->id + 1,
-                                                         con->id + data->batch_size,
-                                                         data->direction);
-}
-
-static int batch_request_connections_by_device_name_internal(mapper_monitor mon,
-                                                             const char* name,
-                                                             int batch_size,
-                                                             int direction)
-{
-    // find the db record of device
-    mapper_db_device dev = mapper_db_get_device_by_name(&mon->db, name);
-    if (!dev) {
-        return 1;
-    }
-
-    int connection_count = 0;
-    char type;
-    const void *value;
-    int length;
-
-    if ((direction == DIRECTION_IN || direction == DIRECTION_BOTH) &&
-        !mapper_db_device_property_lookup(dev, "n_connections_in", &type, &value, &length)) {
-        if (length && type == LO_INT32) {
-            int *vals = (int*)value;
-            connection_count += vals[0];
-        }
-    }
-    if ((direction == DIRECTION_OUT || direction == DIRECTION_BOTH) &&
-        !mapper_db_device_property_lookup(dev, "n_connections_out", &type, &value, &length)) {
-        if (length && type == LO_INT32) {
-            int *vals = (int*)value;
-            connection_count += vals[0];
+    // check if autorenewing subscription exists
+    mapper_monitor_subscription *s = &mon->subscriptions;
+    while (*s) {
+        if (strcmp((*s)->name, device_name)==0) {
+            if (send_message) {
+                snprintf(cmd, 1024, "%s/unsubscribe", device_name);
+                mapper_monitor_set_bundle_dest(mon, device_name);
+                lo_message m = lo_message_new();
+                if (!m)
+                    break;
+                lo_bundle_add_message(mon->admin->bundle, cmd, m);
+                mapper_admin_send_bundle(mon->admin);
+            }
+            // remove from subscriber list
+            mapper_monitor_subscription temp = *s;
+            *s = temp->next;
+            if (temp->name)
+                free(temp->name);
+            free(temp);
+            return;
         }
+        s = &(*s)->next;
     }
-
-    if (!connection_count)
-        return 1;
-
-    if (connection_count <= batch_size) {
-        request_connections_by_device_name_internal(mon, name, direction);
-        return 1;
-    }
-
-    mapper_db_batch_request data = (mapper_db_batch_request)
-                                    malloc(sizeof(struct _mapper_db_batch_request));
-    data->monitor = mon;
-    data->device = dev;
-    data->index = 0;
-    data->total_count = connection_count;
-    data->batch_size = batch_size;
-    data->direction = direction;
-
-    mapper_db_add_connection_callback(&mon->db, on_connection_continue_batch_request, data);
-    request_connection_range_by_device_name_internal(mon, name, 0, batch_size, direction);
-    return 0;
-}
-
-int mapper_monitor_batch_request_connections_by_device_name(mapper_monitor mon,
-                                                            const char* name,
-                                                            int batch_size)
-{
-    return batch_request_connections_by_device_name_internal(mon, name,
-                                                             batch_size,
-                                                             DIRECTION_BOTH);
 }
 
-int mapper_monitor_batch_request_connections_by_src_device_name(mapper_monitor mon,
-                                                                const char* name,
-                                                                int batch_size)
+void mapper_monitor_unsubscribe(mapper_monitor mon, const char *device_name)
 {
-    return batch_request_connections_by_device_name_internal(mon, name,
-                                                             batch_size,
-                                                             DIRECTION_OUT);
+    mapper_monitor_unsubscribe_internal(mon, device_name, 1);
 }
 
-int mapper_monitor_batch_request_connections_by_dest_device_name(mapper_monitor mon,
-                                                                 const char* name,
-                                                                 int batch_size)
+void mapper_monitor_request_devices(mapper_monitor mon)
 {
-    return batch_request_connections_by_device_name_internal(mon, name,
-                                                             batch_size,
-                                                             DIRECTION_IN);
+    mapper_admin_set_bundle_dest_bus(mon->admin);
+    mapper_admin_bundle_message(mon->admin, ADM_WHO, 0, "");
 }
 
 void mapper_monitor_link(mapper_monitor mon,
@@ -546,23 +293,29 @@ void mapper_monitor_link(mapper_monitor mon,
             }
         }
 
-        lo_send_message(mon->admin->admin_addr, "/link", m);
+        mapper_monitor_set_bundle_dest(mon, dest_device);
+
+        // TODO: switch scopes to regular props
+        lo_send_message(mon->admin->bus_addr, "/link", m);
         free(m);
     }
-    else
-        mapper_admin_send( mon->admin, ADM_LINK, 0, "ss",
-                           source_device, dest_device );
+    else {
+        mapper_admin_set_bundle_dest_bus(mon->admin);
+        mapper_admin_bundle_message( mon->admin, ADM_LINK, 0, "ss",
+                                    source_device, dest_device );
+    }
     /* We cannot depend on string arguments sticking around for liblo to
      * serialize later: trigger immediate dispatch. */
     mapper_admin_send_bundle(mon->admin);
 }
 
 void mapper_monitor_unlink(mapper_monitor mon,
-                           const char* source_device,
+                           const char* src_device,
                            const char* dest_device)
 {
-    mapper_admin_send( mon->admin, ADM_UNLINK, 0, "ss",
-                       source_device, dest_device );
+    mapper_monitor_set_bundle_dest(mon, src_device);
+    mapper_admin_bundle_message(mon->admin, ADM_UNLINK, 0, "ss",
+                                src_device, dest_device);
 }
 
 void mapper_monitor_connection_modify(mapper_monitor mon,
@@ -572,37 +325,39 @@ void mapper_monitor_connection_modify(mapper_monitor mon,
                                       unsigned int props_flags)
 {
     if (props) {
-        mapper_admin_send( mon->admin, ADM_CONNECTION_MODIFY, 0, "ss",
-                           source_signal, dest_signal,
-                           (props_flags & CONNECTION_BOUND_MIN)
-                           ? AT_BOUND_MIN : -1, props->bound_min,
-                           (props_flags & CONNECTION_BOUND_MAX)
-                           ? AT_BOUND_MAX : -1, props->bound_max,
-                           ((props_flags & CONNECTION_RANGE_SRC_MIN) &&
-                            (props_flags & CONNECTION_SRC_TYPE) &&
-                            (props_flags & CONNECTION_SRC_LENGTH))
-                           ? AT_SRC_MIN : -1, props,
-                           ((props_flags & CONNECTION_RANGE_SRC_MAX) &&
-                            (props_flags & CONNECTION_SRC_TYPE) &&
-                            (props_flags & CONNECTION_SRC_LENGTH))
-                           ? AT_SRC_MAX : -1, props,
-                           ((props_flags & CONNECTION_RANGE_DEST_MIN) &&
-                            (props_flags & CONNECTION_DEST_TYPE) &&
-                            (props_flags & CONNECTION_DEST_LENGTH))
-                           ? AT_DEST_MIN : -1, props,
-                           ((props_flags & CONNECTION_RANGE_DEST_MAX) &&
-                            (props_flags & CONNECTION_DEST_TYPE) &&
-                            (props_flags & CONNECTION_DEST_LENGTH))
-                           ? AT_DEST_MAX : -1, props,
-                           (props_flags & CONNECTION_EXPRESSION)
-                           ? AT_EXPRESSION : -1, props->expression,
-                           (props_flags & CONNECTION_MODE)
-                           ? AT_MODE : -1, props->mode,
-                           (props_flags & CONNECTION_MUTED)
-                           ? AT_MUTE : -1, props->muted,
-                           (props_flags & CONNECTION_SEND_AS_INSTANCE)
-                           ? AT_SEND_AS_INSTANCE : -1,
-                           props->send_as_instance);
+        // TODO: lookup device ip/ports, send directly?
+        mapper_admin_set_bundle_dest_bus(mon->admin);
+        mapper_admin_bundle_message(mon->admin, ADM_CONNECTION_MODIFY, 0, "ss",
+                                    source_signal, dest_signal,
+                                    (props_flags & CONNECTION_BOUND_MIN)
+                                    ? AT_BOUND_MIN : -1, props->bound_min,
+                                    (props_flags & CONNECTION_BOUND_MAX)
+                                    ? AT_BOUND_MAX : -1, props->bound_max,
+                                    ((props_flags & CONNECTION_RANGE_SRC_MIN) &&
+                                     (props_flags & CONNECTION_SRC_TYPE) &&
+                                     (props_flags & CONNECTION_SRC_LENGTH))
+                                    ? AT_SRC_MIN : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_SRC_MAX) &&
+                                     (props_flags & CONNECTION_SRC_TYPE) &&
+                                     (props_flags & CONNECTION_SRC_LENGTH))
+                                    ? AT_SRC_MAX : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_DEST_MIN) &&
+                                     (props_flags & CONNECTION_DEST_TYPE) &&
+                                     (props_flags & CONNECTION_DEST_LENGTH))
+                                    ? AT_DEST_MIN : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_DEST_MAX) &&
+                                     (props_flags & CONNECTION_DEST_TYPE) &&
+                                     (props_flags & CONNECTION_DEST_LENGTH))
+                                    ? AT_DEST_MAX : -1, props,
+                                    (props_flags & CONNECTION_EXPRESSION)
+                                    ? AT_EXPRESSION : -1, props->expression,
+                                    (props_flags & CONNECTION_MODE)
+                                    ? AT_MODE : -1, props->mode,
+                                    (props_flags & CONNECTION_MUTED)
+                                    ? AT_MUTE : -1, props->muted,
+                                    (props_flags & CONNECTION_SEND_AS_INSTANCE)
+                                    ? AT_SEND_AS_INSTANCE : -1,
+                                    props->send_as_instance);
         /* We cannot depend on string arguments sticking around for liblo to
          * serialize later: trigger immediate dispatch. */
         mapper_admin_send_bundle(mon->admin);
@@ -615,42 +370,44 @@ void mapper_monitor_connect(mapper_monitor mon,
                             mapper_db_connection_t *props,
                             unsigned int props_flags)
 {
+    // TODO: lookup device ip/ports, send directly?
+    mapper_admin_set_bundle_dest_bus(mon->admin);
     if (props) {
-        mapper_admin_send( mon->admin, ADM_CONNECT, 0, "ss",
-                           source_signal, dest_signal,
-                           (props_flags & CONNECTION_BOUND_MIN)
-                           ? AT_BOUND_MIN : -1, props->bound_min,
-                           (props_flags & CONNECTION_BOUND_MAX)
-                           ? AT_BOUND_MAX : -1, props->bound_max,
-                           ((props_flags & CONNECTION_RANGE_SRC_MIN) &&
-                            (props_flags & CONNECTION_SRC_TYPE) &&
-                            (props_flags & CONNECTION_SRC_LENGTH))
-                           ? AT_SRC_MIN : -1, props,
-                           ((props_flags & CONNECTION_RANGE_SRC_MAX) &&
-                            (props_flags & CONNECTION_SRC_TYPE) &&
-                            (props_flags & CONNECTION_SRC_LENGTH))
-                           ? AT_SRC_MAX : -1, props,
-                           ((props_flags & CONNECTION_RANGE_DEST_MIN) &&
-                            (props_flags & CONNECTION_DEST_TYPE) &&
-                            (props_flags & CONNECTION_DEST_LENGTH))
-                           ? AT_DEST_MIN : -1, props,
-                           ((props_flags & CONNECTION_RANGE_DEST_MAX) &&
-                            (props_flags & CONNECTION_DEST_TYPE) &&
-                            (props_flags & CONNECTION_DEST_LENGTH))
-                           ? AT_DEST_MAX : -1, props,
-                           (props_flags & CONNECTION_EXPRESSION)
-                           ? AT_EXPRESSION : -1, props->expression,
-                           (props_flags & CONNECTION_MODE)
-                           ? AT_MODE : -1, props->mode,
-                           (props_flags & CONNECTION_MUTED)
-                           ? AT_MUTE : -1, props->muted,
-                           (props_flags & CONNECTION_SEND_AS_INSTANCE)
-                           ? AT_SEND_AS_INSTANCE : -1,
-                           props->send_as_instance );
+        mapper_admin_bundle_message(mon->admin, ADM_CONNECT, 0, "ss",
+                                    source_signal, dest_signal,
+                                    (props_flags & CONNECTION_BOUND_MIN)
+                                    ? AT_BOUND_MIN : -1, props->bound_min,
+                                    (props_flags & CONNECTION_BOUND_MAX)
+                                    ? AT_BOUND_MAX : -1, props->bound_max,
+                                    ((props_flags & CONNECTION_RANGE_SRC_MIN) &&
+                                     (props_flags & CONNECTION_SRC_TYPE) &&
+                                     (props_flags & CONNECTION_SRC_LENGTH))
+                                    ? AT_SRC_MIN : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_SRC_MAX) &&
+                                     (props_flags & CONNECTION_SRC_TYPE) &&
+                                     (props_flags & CONNECTION_SRC_LENGTH))
+                                    ? AT_SRC_MAX : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_DEST_MIN) &&
+                                     (props_flags & CONNECTION_DEST_TYPE) &&
+                                     (props_flags & CONNECTION_DEST_LENGTH))
+                                    ? AT_DEST_MIN : -1, props,
+                                    ((props_flags & CONNECTION_RANGE_DEST_MAX) &&
+                                     (props_flags & CONNECTION_DEST_TYPE) &&
+                                     (props_flags & CONNECTION_DEST_LENGTH))
+                                    ? AT_DEST_MAX : -1, props,
+                                    (props_flags & CONNECTION_EXPRESSION)
+                                    ? AT_EXPRESSION : -1, props->expression,
+                                    (props_flags & CONNECTION_MODE)
+                                    ? AT_MODE : -1, props->mode,
+                                    (props_flags & CONNECTION_MUTED)
+                                    ? AT_MUTE : -1, props->muted,
+                                    (props_flags & CONNECTION_SEND_AS_INSTANCE)
+                                    ? AT_SEND_AS_INSTANCE : -1,
+                                    props->send_as_instance );
     }
     else
-        mapper_admin_send( mon->admin, ADM_CONNECT, 0, "ss",
-                           source_signal, dest_signal );
+        mapper_admin_bundle_message(mon->admin, ADM_CONNECT, 0, "ss",
+                                    source_signal, dest_signal);
     /* We cannot depend on string arguments sticking around for liblo to
      * serialize later: trigger immediate dispatch. */
     mapper_admin_send_bundle(mon->admin);
@@ -660,41 +417,79 @@ void mapper_monitor_disconnect(mapper_monitor mon,
                                const char* source_signal,
                                const char* dest_signal)
 {
-    mapper_admin_send( mon->admin, ADM_DISCONNECT, 0, "ss",
-                       source_signal, dest_signal );
+    // TODO: lookup device ip/ports, send directly?
+    mapper_admin_set_bundle_dest_bus(mon->admin);
+    mapper_admin_bundle_message(mon->admin, ADM_DISCONNECT, 0, "ss",
+                                source_signal, dest_signal);
 }
 
-static void on_device_autorequest(mapper_db_device dev,
-                                  mapper_db_action_t a,
-                                  void *user)
+static void on_device_autosubscribe(mapper_db_device dev,
+                                    mapper_db_action_t a,
+                                    void *user)
 {
-    if (a == MDB_NEW)
-    {
-        mapper_monitor mon = (mapper_monitor)(user);
-
-        // Request signals, links, connections for new devices.
-        if ((mon->autorequest & AUTOREQ_SIGNALS))
-            mapper_monitor_batch_request_signals_by_device_name(mon, dev->name, 10);
-        if (mon->autorequest & AUTOREQ_LINKS)
-            mapper_monitor_request_links_by_src_device_name(mon, dev->name);
-        if (mon->autorequest & AUTOREQ_CONNECTIONS)
-            mapper_monitor_batch_request_connections_by_src_device_name(mon, dev->name, 10);
+    mapper_monitor mon = (mapper_monitor)(user);
+
+    if (a == MDB_NEW) {
+        // Subscribe to signals, links, and/or connections for new devices.
+        if (mon->autosubscribe)
+            mapper_monitor_subscribe(mon, dev->name, mon->autosubscribe, -1);
+    }
+    else if (a == MDB_REMOVE) {
+        mapper_monitor_unsubscribe_internal(mon, dev->name, 0);
     }
 }
 
-void mapper_monitor_autorequest(mapper_monitor mon,
-                                mapper_monitor_autoreq_mode_t flags)
+void mapper_monitor_autosubscribe(mapper_monitor mon, int autosubscribe_flags)
 {
-    if (flags) {
-        mapper_db_add_device_callback(&mon->db, on_device_autorequest, mon);
-        mapper_admin_send(mon->admin, ADM_WHO, 0, "");
+    // TODO: remove autorenewing subscription record if necessary
+    if (!mon->autosubscribe && autosubscribe_flags)
+        mapper_db_add_device_callback(&mon->db, on_device_autosubscribe, mon);
+    else if (mon->autosubscribe && !autosubscribe_flags) {
+        mapper_db_remove_device_callback(&mon->db, on_device_autosubscribe, mon);
+        while (mon->subscriptions) {
+            mapper_monitor_unsubscribe_internal(mon, mon->subscriptions->name, 1);
+        }
     }
-    else
-        mapper_db_remove_device_callback(&mon->db, on_device_autorequest, mon);
-    mon->autorequest = flags;
+    mon->autosubscribe = autosubscribe_flags;
 }
 
 void mapper_monitor_now(mapper_monitor mon, mapper_timetag_t *tt)
 {
     mapper_clock_now(&mon->admin->clock, tt);
 }
+
+void mapper_monitor_set_timeout(mapper_monitor mon, int timeout_sec)
+{
+    if (timeout_sec < 0)
+        timeout_sec = ADMIN_TIMEOUT_SEC;
+    mon->timeout_sec = timeout_sec;
+}
+
+int mapper_monitor_get_timeout(mapper_monitor mon)
+{
+    return mon->timeout_sec;
+}
+
+void mapper_monitor_flush_db(mapper_monitor mon, int timeout_sec, int quiet)
+{
+    mapper_clock_now(&mon->admin->clock, &mon->admin->clock.now);
+
+    // flush expired device records
+    mapper_db_flush(&mon->db, mon->admin->clock.now.sec, timeout_sec, quiet);
+
+    // also need to remove subscriptions
+    mapper_monitor_subscription *s = &mon->subscriptions;
+    while (*s) {
+        if (!mapper_db_get_device_by_name(&mon->db, (*s)->name)) {
+            // don't bother sending '/unsubscribe' since device is unresponsive
+            // remove from subscriber list
+            mapper_monitor_subscription temp = *s;
+            *s = temp->next;
+            if (temp->name)
+                free(temp->name);
+            free(temp);
+        }
+        else
+            s = &(*s)->next;
+    }
+}
diff --git a/src/receiver.c b/src/receiver.c
index b93ffe7..a5b813e 100644
--- a/src/receiver.c
+++ b/src/receiver.c
@@ -11,14 +11,17 @@
 #include <mapper/mapper.h>
 
 mapper_receiver mapper_receiver_new(mapper_device device, const char *host,
-                                    int port, const char *name)
+                                    int admin_port, int data_port,
+                                    const char *name)
 {
     char str[16];
     mapper_receiver r = (mapper_receiver) calloc(1, sizeof(struct _mapper_link));
     r->props.src_host = strdup(host);
-    r->props.src_port = port;
-    sprintf(str, "%d", port);
-    r->remote_addr = lo_address_new(host, str);
+    r->props.src_port = data_port;
+    sprintf(str, "%d", data_port);
+    r->data_addr = lo_address_new(host, str);
+    sprintf(str, "%d", admin_port);
+    r->admin_addr = lo_address_new(host, str);
     r->props.src_name = strdup(name);
     r->props.src_name_hash = crc32(0L, (const Bytef *)name, strlen(name));
     r->props.dest_name = strdup(mdev_name(device));
@@ -32,9 +35,13 @@ mapper_receiver mapper_receiver_new(mapper_device device, const char *host,
     r->props.extra = table_new();
     r->device = device;
     r->signals = 0;
-    r->n_connections = 0;
+    r->num_connections = 0;
 
-    if (!r->remote_addr) {
+    r->clock.new = 1;
+    r->clock.sent.message_id = 0;
+    r->clock.response.message_id = -1;
+
+    if (!r->data_addr) {
         mapper_receiver_free(r);
         return 0;
     }
@@ -50,8 +57,10 @@ void mapper_receiver_free(mapper_receiver r)
             free(r->props.src_name);
         if (r->props.src_host)
             free(r->props.src_host);
-        if (r->remote_addr)
-            lo_address_free(r->remote_addr);
+        if (r->admin_addr)
+            lo_address_free(r->admin_addr);
+        if (r->data_addr)
+            lo_address_free(r->data_addr);
         if (r->props.dest_name)
             free(r->props.dest_name);
         while (r->signals && r->signals->connections) {
@@ -104,66 +113,18 @@ int mapper_receiver_set_from_message(mapper_receiver r, mapper_message_t *msg)
     return updated;
 }
 
-static void message_add_coerced_signal_value(lo_message m,
-                                             mapper_signal sig,
-                                             mapper_signal_instance si,
-                                             const char coerce_type)
-{
-    int i;
-    if (sig->props.type == 'f') {
-        float *v = si->value;
-        if (coerce_type == 'f') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_float(m, v[i]);
-        }
-        else if (coerce_type == 'i') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_int32(m, (int)v[i]);
-        }
-        else if (coerce_type == 'd') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_double(m, (double)v[i]);
-        }
-    }
-    else if (sig->props.type == 'i') {
-        int *v = si->value;
-        if (coerce_type == 'i') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_int32(m, v[i]);
-        }
-        else if (coerce_type == 'f') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_float(m, (float)v[i]);
-        }
-        else if (coerce_type == 'd') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_double(m, (double)v[i]);
-        }
-    }
-    else if (sig->props.type == 'd') {
-        double *v = si->value;
-        if (coerce_type == 'd') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_double(m, (int)v[i]);
-        }
-        else if (coerce_type == 'i') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_int32(m, (int)v[i]);
-        }
-        else if (coerce_type == 'f') {
-            for (i = 0; i < sig->props.length; i++)
-                lo_message_add_float(m, (float)v[i]);
-        }
-    }
-}
-
 void mapper_receiver_send_update(mapper_receiver r,
                                  mapper_signal sig,
                                  int instance_index,
                                  mapper_timetag_t tt)
 {
-    int i, count=0;
+    // TODO: check vector has_values flags, include defined elements even if !has_value?
+    int count=0;
     mapper_id_map map = sig->id_maps[instance_index].map;
+    if (!map)
+        return;
+
+    int in_scope = map ? mapper_receiver_in_scope(r, map->origin) : 0;
 
     // find the signal connection
     mapper_receiver_signal rc = r->signals;
@@ -188,63 +149,28 @@ void mapper_receiver_send_update(mapper_receiver r,
 
     c = rc->connections;
     while (c) {
-        if (c->props.mode != MO_REVERSE) {
+        if (c->props.mode != MO_REVERSE || (c->props.send_as_instance && !in_scope)) {
             c = c->next;
             continue;
         }
 
-        if (sig->props.num_instances == 1) {
-            lo_message m = lo_message_new();
-            if (!m)
-                return;
-            message_add_coerced_signal_value(m, sig, sig->id_maps[0].instance,
-                                             c->props.src_type);
-            lo_bundle_add_message(b, c->props.query_name, m);
-        }
-        else if (instance_index >= 0) {
-            mapper_signal_instance si = sig->id_maps[instance_index].instance;
-            lo_message m = lo_message_new();
-            if (!m)
-                return;
+        lo_message m = lo_message_new();
+        if (!m)
+            return;
+
+        mapper_signal_instance si = sig->id_maps[instance_index].instance;
+        message_add_coerced_signal_instance_value(m, sig, si,
+                                                  c->props.src_length,
+                                                  c->props.src_type);
+        if (c->props.send_as_instance) {
+            lo_message_add_string(m, "@instance");
             lo_message_add_int32(m, map->origin);
             lo_message_add_int32(m, map->public);
-            if (si->has_value)
-                message_add_coerced_signal_value(m, sig, si, c->props.src_type);
-            else
-                lo_message_add_nil(m);
-            lo_bundle_add_message(b, c->props.query_name, m);
-        }
-        else {
-            int sent = 0;
-            for (i = 0; i < sig->id_map_length; i++) {
-                mapper_signal_instance si = sig->id_maps[i].instance;
-                if (!si)
-                    continue;
-                lo_message m = lo_message_new();
-                if (!m)
-                    return;
-                lo_message_add_int32(m, map->origin);
-                lo_message_add_int32(m, map->public);
-                if (si->has_value)
-                    message_add_coerced_signal_value(m, sig, si,
-                                                     c->props.src_type);
-                else
-                    lo_message_add_nil(m);
-                lo_bundle_add_message(b, c->props.query_name, m);
-                sent++;
-            }
-            if (!sent) {
-                // If there are no active instances, send null response
-                lo_message m = lo_message_new();
-                if (!m)
-                    return;
-                lo_message_add_nil(m);
-                lo_bundle_add_message(b, c->props.query_name, m);
-            }
         }
+        lo_bundle_add_message(b, c->props.query_name, m);
         c = c->next;
     }
-    lo_send_bundle(r->remote_addr, b);
+    lo_send_bundle(r->data_addr, b);
     lo_bundle_free_messages(b);
 }
 
@@ -270,19 +196,22 @@ void mapper_receiver_send_released(mapper_receiver r, mapper_signal sig,
     lo_bundle b = lo_bundle_new(tt);
 
     c = rs->connections;
+    int i;
     while (c) {
         lo_message m = lo_message_new();
         if (!m)
             return;
+        for (i = 0; i < c->props.src_length; i++)
+            lo_message_add_nil(m);
+        lo_message_add_string(m, "@instance");
         lo_message_add_int32(m, map->origin);
         lo_message_add_int32(m, map->public);
-        lo_message_add_false(m);
         lo_bundle_add_message(b, c->props.src_name, m);
         c = c->next;
     }
 
     if (lo_bundle_count(b))
-        lo_send_bundle_from(r->remote_addr, r->device->server, b);
+        lo_send_bundle_from(r->data_addr, r->device->server, b);
 
     lo_bundle_free_messages(b);
 }
@@ -338,14 +267,14 @@ mapper_connection mapper_receiver_add_connection(mapper_receiver r,
     c->next = rs->connections;
     rs->connections = c;
     c->parent = rs;
-    r->n_connections++;
+    r->num_connections++;
 
     return c;
 }
 
 static void mapper_receiver_free_connection(mapper_receiver r, mapper_connection c)
 {
-    int i;
+    int i, j;
     if (r && c) {
         if (c->props.src_name)
             free(c->props.src_name);
@@ -369,13 +298,20 @@ static void mapper_receiver_free_connection(mapper_receiver r, mapper_connection
         for (i=0; i<c->parent->num_instances; i++) {
             free(c->history[i].value);
             free(c->history[i].timetag);
+            if (c->num_expr_vars) {
+                for (j=0; j<c->num_expr_vars; j++) {
+                    free(c->expr_vars[i][j].value);
+                    free(c->expr_vars[i][j].timetag);
+                }
+                free(c->expr_vars[i]);
+            }
         }
+        if (c->expr_vars)
+            free(c->expr_vars);
         if (c->history)
             free(c->history);
-        if (c->blob)
-            free(c->blob);
         free(c);
-        r->n_connections--;
+        r->num_connections--;
         return;
     }
 }
@@ -472,7 +408,7 @@ int mapper_receiver_remove_connection(mapper_receiver r,
         if (*temp == c) {
             *temp = c->next;
             mapper_receiver_free_connection(r, c);
-            r->n_connections--;
+            r->num_connections--;
             found = 1;
             break;
         }
@@ -620,3 +556,14 @@ mapper_receiver mapper_receiver_find_by_src_name(mapper_receiver r,
     }
     return 0;
 }
+
+mapper_receiver mapper_receiver_find_by_src_hash(mapper_receiver r,
+                                                 uint32_t name_hash)
+{
+    while (r) {
+        if (name_hash == r->props.src_name_hash)
+            return r;
+        r = r->next;
+    }
+    return 0;
+}
diff --git a/src/router.c b/src/router.c
index 05cd27a..580f9bd 100644
--- a/src/router.c
+++ b/src/router.c
@@ -16,15 +16,18 @@ static void mapper_router_send_or_bundle_message(mapper_router router,
                                                  mapper_timetag_t tt);
 
 mapper_router mapper_router_new(mapper_device device, const char *host,
-                                int port, const char *name)
+                                int admin_port, int data_port,
+                                const char *name)
 {
     char str[16];
     mapper_router r = (mapper_router) calloc(1, sizeof(struct _mapper_link));
     r->props.src_name = strdup(mdev_name(device));
     r->props.dest_host = strdup(host);
-    r->props.dest_port = port;
-    sprintf(str, "%d", port);
-    r->remote_addr = lo_address_new(host, str);
+    r->props.dest_port = data_port;
+    sprintf(str, "%d", data_port);
+    r->data_addr = lo_address_new(host, str);
+    sprintf(str, "%d", admin_port);
+    r->admin_addr = lo_address_new(host, str);
     r->props.dest_name = strdup(name);
     r->props.dest_name_hash = crc32(0L, (const Bytef *)name, strlen(name));
 
@@ -37,9 +40,13 @@ mapper_router mapper_router_new(mapper_device device, const char *host,
     r->props.extra = table_new();
     r->device = device;
     r->signals = 0;
-    r->n_connections = 0;
+    r->num_connections = 0;
 
-    if (!r->remote_addr) {
+    r->clock.new = 1;
+    r->clock.sent.message_id = 0;
+    r->clock.response.message_id = -1;
+
+    if (!r->data_addr) {
         mapper_router_free(r);
         return 0;
     }
@@ -57,8 +64,10 @@ void mapper_router_free(mapper_router r)
             free(r->props.dest_host);
         if (r->props.dest_name)
             free(r->props.dest_name);
-        if (r->remote_addr)
-            lo_address_free(r->remote_addr);
+        if (r->admin_addr)
+            lo_address_free(r->admin_addr);
+        if (r->data_addr)
+            lo_address_free(r->data_addr);
         while (r->signals && r->signals->connections) {
             // router_signal is freed with last child connection
             mapper_router_remove_connection(r, r->signals->connections);
@@ -144,7 +153,7 @@ void mapper_router_num_instances_changed(mapper_router r,
     // Need to allocate more instances
     rs->history = realloc(rs->history, sizeof(struct _mapper_signal_history)
                           * size);
-    int i;
+    int i, j;
     for (i=rs->num_instances; i<size; i++) {
         rs->history[i].type = sig->props.type;
         rs->history[i].length = sig->props.length;
@@ -161,6 +170,8 @@ void mapper_router_num_instances_changed(mapper_router r,
     while (c) {
         c->history = realloc(c->history, sizeof(struct _mapper_signal_history)
                              * size);
+        c->expr_vars = realloc(c->expr_vars, sizeof(mapper_signal_history_t*)
+                               * size);
         for (i=rs->num_instances; i<size; i++) {
             c->history[i].type = c->props.dest_type;
             c->history[i].length = c->props.dest_length;
@@ -170,6 +181,24 @@ void mapper_router_num_instances_changed(mapper_router r,
             c->history[i].timetag = calloc(1, sizeof(mapper_timetag_t)
                                            * c->props.dest_history_size);
             c->history[i].position = -1;
+
+            c->expr_vars[i] = malloc(sizeof(struct _mapper_signal_history) *
+                                     c->num_expr_vars);
+        }
+        if (rs->num_instances > 0 && c->num_expr_vars > 0) {
+            for (i=rs->num_instances; i<size; i++) {
+                for (j=0; j<c->num_expr_vars; j++) {
+                    c->expr_vars[i][j].type = c->expr_vars[0][j].type;
+                    c->expr_vars[i][j].length = c->expr_vars[0][j].length;
+                    c->expr_vars[i][j].size = c->expr_vars[0][j].size;
+                    c->expr_vars[i][j].position = -1;
+                    c->expr_vars[i][j].value = calloc(1, sizeof(double) *
+                                                     c->expr_vars[i][j].length *
+                                                     c->expr_vars[i][j].size);
+                    c->expr_vars[i][j].timetag = calloc(1, sizeof(mapper_timetag_t) *
+                                                       c->expr_vars[i][j].size);
+                }
+            }
         }
         c = c->next;
     }
@@ -186,6 +215,7 @@ void mapper_router_process_signal(mapper_router r,
 {
     mapper_id_map map = sig->id_maps[instance_index].map;
     int in_scope = mapper_router_in_scope(r, map->origin);
+    lo_message m;
 
     // find the signal connection
     mapper_router_signal rs = r->signals;
@@ -211,9 +241,14 @@ void mapper_router_process_signal(mapper_router r,
         while (c) {
             c->history[id].position = -1;
             if ((c->props.mode != MO_REVERSE) &&
-                (!c->props.send_as_instance || in_scope))
-                mapper_router_send_update(r, c, id, c->props.send_as_instance ?
-                                          map : 0, tt, 0);
+                (!c->props.send_as_instance || in_scope)) {
+                m = mapper_router_build_message(0, c->props.dest_length,
+                                                c->props.dest_type, 0,
+                                                c->props.send_as_instance ? map : 0);
+                if (m)
+                    mapper_router_send_or_bundle_message(r, c->props.dest_name,
+                                                         m, tt);
+            }
             // also need to reset associated output memory
             memset(c->history[id].value, 0, c->props.dest_history_size *
                    c->props.dest_length * mapper_type_size(c->props.dest_type));
@@ -225,122 +260,130 @@ void mapper_router_process_signal(mapper_router r,
         return;
     }
 
-    if (count > 1) {
-        // allocate blob for each connection
-        c = rs->connections;
-        while (c) {
-            if ((c->props.mode != MO_REVERSE) &&
-                (!c->props.send_as_instance || in_scope))
-                c->blob = realloc(c->blob, mapper_type_size(c->props.dest_type)
-                                  * c->props.dest_length * count);
+    // if count > 1, we need to allocate sufficient memory for largest output
+    // vector so that we can store calculated values before sending
+    // TODO: calculate max_output_size, cache in link_signal
+    void *out_value_p = count == 1 ? 0 : alloca(count * sig->props.length * sizeof(double));
+    int i, j;
+    c = rs->connections;
+    while (c) {
+        if ((c->props.mode == MO_REVERSE)
+            || (c->props.send_as_instance && !in_scope)) {
             c = c->next;
+            continue;
         }
-    }
-
-    int i;
-    for (i=0; i<count; i++) {
-        // copy input history
-        size_t n = msig_vector_bytes(sig);
-        rs->history[id].position = (rs->history[id].position + 1)
-                                   % rs->history[id].size;
-        memcpy(msig_history_value_pointer(rs->history[id]),
-               value + n * i, n);
-        memcpy(msig_history_tt_pointer(rs->history[id]),
-               &tt, sizeof(mapper_timetag_t));
 
-        c = rs->connections;
-        while (c) {
-            if ((c->props.mode == MO_REVERSE) ||
-                (c->props.send_as_instance && !in_scope)) {
-                c = c->next;
+        char typestring[c->props.dest_length * count];
+        j = 0;
+        for (i = 0; i < count; i++) {
+            // copy input history
+            size_t n = msig_vector_bytes(sig);
+            rs->history[id].position = ((rs->history[id].position + 1)
+                                        % rs->history[id].size);
+            memcpy(msig_history_value_pointer(rs->history[id]),
+                   value + n * i, n);
+            memcpy(msig_history_tt_pointer(rs->history[id]),
+                   &tt, sizeof(mapper_timetag_t));
+
+            // handle cases in which part of count update does not cause output
+            if (!(mapper_connection_perform(c, &rs->history[id],
+                                            &c->expr_vars[id],
+                                            &c->history[id],
+                                            typestring+c->props.dest_length*j)))
+                continue;
+            if (!(mapper_boundary_perform(c, &c->history[id])))
                 continue;
+
+            if (count > 1) {
+                memcpy((char*)out_value_p + mapper_type_size(c->props.dest_type) *
+                       c->props.dest_length * i,
+                       msig_history_value_pointer(c->history[id]),
+                       mapper_type_size(c->props.dest_type) *
+                       c->props.dest_length);
             }
-            if (mapper_connection_perform(c, &rs->history[id],
-                                          &c->history[id]))
-            {
-                if (mapper_boundary_perform(c, &c->history[id])) {
-                    if (count > 1)
-                        memcpy(c->blob + mapper_type_size(c->props.dest_type) *
-                               c->props.dest_length * i,
-                               msig_history_value_pointer(c->history[id]),
-                               mapper_type_size(c->props.dest_type) *
-                               c->props.dest_length);
-                    else
-                        mapper_router_send_update(r, c, id, c->props.send_as_instance ?
-                                                  map : 0, tt, 0);
-                }
+            else {
+                m = mapper_router_build_message(msig_history_value_pointer(c->history[id]),
+                                                c->props.dest_length,
+                                                c->props.dest_type,
+                                                typestring,
+                                                c->props.send_as_instance ? map : 0);
+                if (m)
+                    mapper_router_send_or_bundle_message(r, c->props.dest_name, m, tt);
             }
-            c = c->next;
+            j++;
         }
-    }
-    if (count > 1) {
-        c = rs->connections;
-        while (c) {
-            if ((c->props.mode != MO_REVERSE) &&
-                (!c->props.send_as_instance || in_scope)) {
-                lo_blob blob = lo_blob_new(mapper_type_size(c->props.dest_type)
-                                           * c->props.dest_length * count, c->blob);
-                mapper_router_send_update(r, c, id, c->props.send_as_instance ?
-                                          map : 0, tt, blob);
-            }
-            c = c->next;
+        if (count > 1 && (c->props.mode != MO_REVERSE) &&
+            (!c->props.send_as_instance || in_scope)) {
+            m = mapper_router_build_message(out_value_p,
+                                            c->props.dest_length * j,
+                                            c->props.dest_type,
+                                            typestring,
+                                            c->props.send_as_instance ? map : 0);
+            if (m)
+                mapper_router_send_or_bundle_message(r, c->props.dest_name,
+                                                     m, tt);
         }
+        c = c->next;
     }
 }
 
 /*! Build a value update message for a given connection. */
-void mapper_router_send_update(mapper_router r,
-                               mapper_connection c,
-                               int history_index,
-                               mapper_id_map id_map,
-                               mapper_timetag_t tt,
-                               lo_blob blob)
+lo_message mapper_router_build_message(void *value, int length, char type,
+                                       char *typestring, mapper_id_map id_map)
 {
     int i;
-    if (!r->remote_addr)
-        return;
 
     lo_message m = lo_message_new();
     if (!m)
-        return;
-
-    if (id_map) {
-        lo_message_add_int32(m, id_map->origin);
-        lo_message_add_int32(m, id_map->public);
-    }
+        return 0;
 
-    if (c->history[history_index].position != -1) {
-        if (blob) {
-            lo_message_add_blob(m, blob);
-            lo_blob_free(blob);
-        }
-        else if (c->history[history_index].type == 'f') {
-            float *v = msig_history_value_pointer(c->history[history_index]);
-            for (i = 0; i < c->history[history_index].length; i++)
-                lo_message_add_float(m, v[i]);
+    if (value && typestring) {
+        if (type == 'f') {
+            float *v = (float*)value;
+            for (i = 0; i < length; i++) {
+                if (typestring[i] == 'N')
+                    lo_message_add_nil(m);
+                else
+                    lo_message_add_float(m, v[i]);
+            }
         }
-        else if (c->history[history_index].type == 'i') {
-            int *v = msig_history_value_pointer(c->history[history_index]);
-            for (i = 0; i < c->history[history_index].length; i++)
-                lo_message_add_int32(m, v[i]);
+        else if (type == 'i') {
+            int *v = (int*)value;
+            for (i = 0; i < length; i++) {
+                if (typestring[i] == 'N')
+                    lo_message_add_nil(m);
+                else
+                    lo_message_add_int32(m, v[i]);
+            }
         }
-        else if (c->history[history_index].type == 'd') {
-            double *v = msig_history_value_pointer(c->history[history_index]);
-            for (i = 0; i < c->history[history_index].length; i++)
-                lo_message_add_double(m, v[i]);
+        else if (type == 'd') {
+            double *v = (double*)value;
+            for (i = 0; i < length; i++) {
+                if (typestring[i] == 'N')
+                    lo_message_add_nil(m);
+                else
+                    lo_message_add_double(m, v[i]);
+            }
         }
     }
     else if (id_map) {
-        lo_message_add_nil(m);
+        for (i = 0; i < length; i++)
+            lo_message_add_nil(m);
     }
 
-    mapper_router_send_or_bundle_message(r, c->props.dest_name, m, tt);
+    if (id_map) {
+        lo_message_add_string(m, "@instance");
+        lo_message_add_int32(m, id_map->origin);
+        lo_message_add_int32(m, id_map->public);
+    }
+    return m;
 }
 
 int mapper_router_send_query(mapper_router r,
                              mapper_signal sig,
                              mapper_timetag_t tt)
 {
+    // TODO: cache the response string
     // find this signal in list of connections
     mapper_router_signal rs = r->signals;
     while (rs && rs->signal != sig)
@@ -361,6 +404,10 @@ int mapper_router_send_query(mapper_router r,
         if (!m)
             continue;
         lo_message_add_string(m, response_string);
+        lo_message_add_int32(m, sig->props.length);
+        // include response address as argument to allow TCP queries?
+        // TODO: always use TCP for queries?
+        lo_message_add_char(m, sig->props.type);
         mapper_router_send_or_bundle_message(r, c->props.query_name, m, tt);
         count++;
         c = c->next;
@@ -394,7 +441,7 @@ void mapper_router_send_or_bundle_message(mapper_router r,
         // Send message immediately
         lo_bundle b = lo_bundle_new(tt);
         lo_bundle_add_message(b, path, m);
-        lo_send_bundle_from(r->remote_addr, r->device->server, b);
+        lo_send_bundle_from(r->data_addr, r->device->server, b);
         lo_bundle_free_messages(b);
     }
 }
@@ -446,7 +493,7 @@ void mapper_router_send_queue(mapper_router r,
 #ifdef HAVE_LIBLO_BUNDLE_COUNT
         if (lo_bundle_count(q->bundle))
 #endif
-            lo_send_bundle_from(r->remote_addr,
+            lo_send_bundle_from(r->data_addr,
                                 r->device->server, q->bundle);
         lo_bundle_free_messages(q->bundle);
         mapper_router_release_queue(r, q);
@@ -515,6 +562,8 @@ mapper_connection mapper_router_add_connection(mapper_router r,
 
     c->history = malloc(sizeof(struct _mapper_signal_history)
                         * rs->num_instances);
+    c->expr_vars = calloc(1, sizeof(mapper_signal_history_t*) * rs->num_instances);
+    c->num_expr_vars = 0;
     int i;
     for (i=0; i<rs->num_instances; i++) {
         // allocate history vectors
@@ -531,24 +580,19 @@ mapper_connection mapper_router_add_connection(mapper_router r,
     c->next = rs->connections;
     rs->connections = c;
     c->parent = rs;
-    r->n_connections++;
-
+    r->num_connections++;
     return c;
 }
 
 static void mapper_router_free_connection(mapper_router r,
                                           mapper_connection c)
 {
-    int i;
+    int i, j;
     if (r && c) {
         if (c->props.src_name)
             free(c->props.src_name);
         if (c->props.dest_name)
             free(c->props.dest_name);
-        if (c->expr)
-            mapper_expr_free(c->expr);
-        if (c->props.expression)
-            free(c->props.expression);
         if (c->props.query_name)
             free(c->props.query_name);
         if (c->props.src_min)
@@ -563,11 +607,22 @@ static void mapper_router_free_connection(mapper_router r,
         for (i=0; i<c->parent->num_instances; i++) {
             free(c->history[i].value);
             free(c->history[i].timetag);
+            if (c->num_expr_vars) {
+                for (j=0; j<c->num_expr_vars; j++) {
+                    free(c->expr_vars[i][j].value);
+                    free(c->expr_vars[i][j].timetag);
+                }
+            }
+            free(c->expr_vars[i]);
         }
+        if (c->expr_vars)
+            free(c->expr_vars);
         if (c->history)
             free(c->history);
-        if (c->blob)
-            free(c->blob);
+        if (c->expr)
+            mapper_expr_free(c->expr);
+        if (c->props.expression)
+            free(c->props.expression);
         free(c);
     }
 }
@@ -582,7 +637,7 @@ int mapper_router_remove_connection(mapper_router r,
         if (*temp == c) {
             *temp = c->next;
             mapper_router_free_connection(r, c);
-            r->n_connections--;
+            r->num_connections--;
             found = 1;
             break;
         }
@@ -601,7 +656,7 @@ int mapper_router_remove_connection(mapper_router r,
             if (*rstemp == rs) {
                 *rstemp = rs->next;
                 int i;
-                for (i=0; i < rs->num_instances; i++) {
+                for (i=0; i<rs->num_instances; i++) {
                     free(rs->history[i].value);
                     free(rs->history[i].timetag);
                 }
@@ -673,3 +728,14 @@ mapper_router mapper_router_find_by_dest_name(mapper_router router,
     }
     return 0;
 }
+
+mapper_router mapper_router_find_by_dest_hash(mapper_router router,
+                                              uint32_t name_hash)
+{
+    while (router) {
+        if (name_hash == router->props.dest_name_hash)
+            return router;
+        router = router->next;
+    }
+    return 0;
+}
diff --git a/src/signal.c b/src/signal.c
index 0c33c6a..9ada60c 100644
--- a/src/signal.c
+++ b/src/signal.c
@@ -48,10 +48,11 @@ static mapper_signal_instance find_instance_by_id(mapper_signal sig, int instanc
 
 mapper_signal msig_new(const char *name, int length, char type,
                        int is_output, const char *unit,
-                       void *minimum, void *maximum,
+                       void *minimum, void *maximum, int num_instances,
                        mapper_signal_update_handler *handler,
                        void *user_data)
 {
+    int i;
     if (length < 1) return 0;
     if (!name) return 0;
     if (type != 'f' && type != 'i' && type != 'd')
@@ -62,13 +63,24 @@ mapper_signal msig_new(const char *name, int length, char type,
 
     mapper_db_signal_init(&sig->props, is_output, type, length, name, unit);
     sig->handler = handler;
-    sig->props.num_instances = 0;
+    sig->has_complete_value = calloc(1, length / 8 + 1);
+    for (i = 0; i < length; i++) {
+        sig->has_complete_value[i/8] |= 1 << (i % 8);
+    }
     sig->props.user_data = user_data;
     msig_set_minimum(sig, minimum);
     msig_set_maximum(sig, maximum);
 
-    // Reserve one instance to start
-    msig_reserve_instances(sig, 1, 0, 0);
+    if (num_instances < 0) {
+        // this is a single-instance signal
+        sig->props.num_instances = 0;
+
+        // Reserve one instance to start
+        msig_reserve_instances(sig, 1, 0, 0);
+    }
+    else {
+        sig->props.num_instances = msig_reserve_instances(sig, num_instances, 0, 0);
+    }
 
     // Reserve one instance id map
     sig->id_map_length = 1;
@@ -90,8 +102,10 @@ void msig_free(mapper_signal sig)
     }
     free(sig->id_maps);
     for (i = 0; i < sig->props.num_instances; i++) {
-        if(sig->instances[i]->value)
+        if (sig->instances[i]->value)
             free(sig->instances[i]->value);
+        if (sig->instances[i]->has_value_flags)
+            free(sig->instances[i]->has_value_flags);
         free(sig->instances[i]);
     }
     free(sig->instances);
@@ -103,6 +117,8 @@ void msig_free(mapper_signal sig)
         free((char*)sig->props.name);
     if (sig->props.unit)
         free((char*)sig->props.unit);
+    if (sig->has_complete_value)
+        free(sig->has_complete_value);
     if (sig->props.extra)
         table_free(sig->props.extra, 1);
     free(sig);
@@ -532,6 +548,7 @@ static int msig_reserve_instance_internal(mapper_signal sig, int *id,
         (mapper_signal_instance) calloc(1, sizeof(struct _mapper_signal_instance));
     si = sig->instances[sig->props.num_instances];
     si->value = calloc(1, msig_vector_bytes(sig));
+    si->has_value_flags = calloc(1, sig->props.length / 8 + 1);
     si->has_value = 0;
 
     if (id)
@@ -594,7 +611,7 @@ int msig_reserve_instances(mapper_signal sig, int num, int *ids, void **user_dat
         count++;
     }
     if (highest != -1)
-        mdev_num_instances_changed(sig->device, sig, highest);
+        mdev_num_instances_changed(sig->device, sig, highest + 1);
     return count;
 }
 
@@ -789,6 +806,8 @@ void msig_remove_instance(mapper_signal sig, int instance_id)
 
     if (sig->instances[i]->value)
         free(sig->instances[i]->value);
+    if (sig->instances[i]->has_value_flags)
+        free(sig->instances[i]->has_value_flags);
     free(sig->instances[i]);
     i++;
     for (; i < sig->props.num_instances; i++) {
@@ -1045,10 +1064,15 @@ mapper_db_signal msig_properties(mapper_signal sig)
 void msig_set_property(mapper_signal sig, const char *property,
                        char type, void *value, int length)
 {
-    if (strcmp(property, "name") == 0 ||
+    if (strcmp(property, "device_name") == 0 ||
+        strcmp(property, "name") == 0 ||
         strcmp(property, "type") == 0 ||
-        strcmp(property, "length") == 0)
+        strcmp(property, "length") == 0 ||
+        strcmp(property, "direction") == 0 ||
+        strcmp(property, "user_data") == 0) {
+        trace("Cannot set locked signal property '%s'\n", property);
         return;
+    }
 
     if (strcmp(property, "min") == 0 ||
         strcmp(property, "minimum") == 0) {
@@ -1151,3 +1175,56 @@ int msig_num_connections(mapper_signal sig)
     }
     return count;
 }
+
+void message_add_coerced_signal_instance_value(lo_message m,
+                                               mapper_signal sig,
+                                               mapper_signal_instance si,
+                                               int length,
+                                               const char type)
+{
+    int i;
+    int min_length = length < sig->props.length ?
+                     length : sig->props.length;
+
+    if (sig->props.type == 'f') {
+        float *v = (float *) si->value;
+        for (i = 0; i < min_length; i++) {
+            if (!si->has_value && !(si->has_value_flags[i/8] & 1 << (i % 8)))
+                lo_message_add_nil(m);
+            else if (type == 'f')
+                lo_message_add_float(m, v[i]);
+            else if (type == 'i')
+                lo_message_add_int32(m, (int)v[i]);
+            else if (type == 'd')
+                lo_message_add_double(m, (double)v[i]);
+        }
+    }
+    else if (sig->props.type == 'i') {
+        int *v = (int *) si->value;
+        for (i = 0; i < min_length; i++) {
+            if (!si->has_value && !(si->has_value_flags[i/8] & 1 << (i % 8)))
+                lo_message_add_nil(m);
+            else if (type == 'i')
+                lo_message_add_int32(m, v[i]);
+            else if (type == 'f')
+                lo_message_add_float(m, (float)v[i]);
+            else if (type == 'd')
+                lo_message_add_double(m, (double)v[i]);
+        }
+    }
+    else if (sig->props.type == 'd') {
+        double *v = (double *) si->value;
+        for (i = 0; i < min_length; i++) {
+            if (!si->has_value && !(si->has_value_flags[i/8] & 1 << (i % 8)))
+                lo_message_add_nil(m);
+            else if (type == 'd')
+                lo_message_add_double(m, (int)v[i]);
+            else if (type == 'i')
+                lo_message_add_int32(m, (int)v[i]);
+            else if (type == 'f')
+                lo_message_add_float(m, (float)v[i]);
+        }
+    }
+    for (i = min_length; i < length; i++)
+        lo_message_add_nil(m);
+}
diff --git a/src/time.c b/src/time.c
index 2192497..d0ea791 100644
--- a/src/time.c
+++ b/src/time.c
@@ -15,41 +15,14 @@ static double multiplier = 1.0/((double)(1LL<<32));
 
 void mapper_clock_init(mapper_clock clock)
 {
-    clock->rate = 1.0;
-    clock->offset = 0.0;
-    clock->confidence = 0.001;
-
     mapper_clock_now(clock, &clock->now);
     clock->next_ping = clock->now.sec;
 }
 
-void mapper_clock_adjust(mapper_clock clock,
-                         double difference,
-                         float confidence)
-{
-    double weight = 1.0 - clock->confidence;
-    double new_offset = clock->offset + difference * weight;
-
-    // try inserting pull from system clock
-    //new_offset *= 0.9999;
-
-    double adjustment = new_offset - clock->offset;
-
-    // adjust stored timetag
-    clock->confidence *= adjustment < 0.001 ? 1.1 : 0.99;
-    if (clock->confidence > 0.9) {
-        clock->confidence = 0.9;
-    }
-    //clock->offset = new_offset;
-}
-
 void mapper_clock_now(mapper_clock clock,
                       mapper_timetag_t *timetag)
 {
-    // first get current time from system clock
-    // adjust using rate and offset from mapping network sync
     lo_timetag_now((lo_timetag*)timetag);
-    mapper_timetag_add_seconds(timetag, clock->offset);
 }
 
 double mapper_timetag_difference(mapper_timetag_t a, mapper_timetag_t b)
diff --git a/src/types_internal.h b/src/types_internal.h
index acf176a..7f22820 100644
--- a/src/types_internal.h
+++ b/src/types_internal.h
@@ -71,22 +71,19 @@ typedef enum {
     ADM_DEVICE,
     ADM_DISCONNECT,
     ADM_DISCONNECTED,
-    ADM_GET_MY_CONNECTIONS,
-    ADM_GET_MY_CONNECTIONS_IN,
-    ADM_GET_MY_CONNECTIONS_OUT,
-    ADM_GET_MY_DEVICE,
-    ADM_GET_MY_LINKS,
-    ADM_GET_MY_LINKS_IN,
-    ADM_GET_MY_LINKS_OUT,
-    ADM_GET_MY_SIGNALS,
-    ADM_GET_MY_SIGNALS_IN,
-    ADM_GET_MY_SIGNALS_OUT,
     ADM_LINK,
     ADM_LINK_MODIFY,
     ADM_LINK_TO,
     ADM_LINKED,
+    ADM_LINK_PING,
     ADM_LOGOUT,
     ADM_SIGNAL,
+    ADM_INPUT,
+    ADM_OUTPUT,
+    ADM_INPUT_REMOVED,
+    ADM_OUTPUT_REMOVED,
+    ADM_SUBSCRIBE,
+    ADM_UNSUBSCRIBE,
     ADM_SYNC,
     ADM_UNLINK,
     ADM_UNLINKED,
@@ -123,34 +120,44 @@ typedef struct _mapper_admin_allocated_t {
 
 /*! Clock and timing information. */
 typedef struct _mapper_sync_timetag_t {
-    int device_id;
     int message_id;
     lo_timetag timetag;
 } mapper_sync_timetag_t;
 
 typedef struct _mapper_clock_t {
-    int wait_time;
-    double rate;
-    double offset;
-    float confidence;
     mapper_timetag_t now;
     uint32_t next_ping;
-    int message_id;
-    int local_index;
-    mapper_sync_timetag_t local[10];
-    mapper_sync_timetag_t remote;
 } mapper_clock_t, *mapper_clock;
 
+typedef struct _mapper_sync_clock_t {
+    double rate;
+    double offset;
+    double latency;
+    double jitter;
+    mapper_sync_timetag_t sent;
+    mapper_sync_timetag_t response;
+    int new;
+} mapper_sync_clock_t, *mapper_sync_clock;
+
+typedef struct _mapper_admin_subscriber {
+    lo_address                      address;
+    uint32_t                        lease_expiration_sec;
+    int                             flags;
+    struct _mapper_admin_subscriber *next;
+} *mapper_admin_subscriber;
+
 /*! A structure that keeps information about a device. */
 typedef struct _mapper_admin {
     int random_id;                    /*!< Random ID for allocation
                                            speedup. */
+    lo_server_thread bus_server;      /*!< LibLo server thread for the
+                                       *   admin bus. */
     int msgs_recvd;                   /*!< Number of messages received on the
                                            admin bus. */
-    lo_server_thread admin_server;    /*!< LibLo server thread for the
-                                       *   admin bus. */
-    lo_address admin_addr;            /*!< LibLo address for the admin
+    lo_address bus_addr;              /*!< LibLo address for the admin
                                        *   bus. */
+    lo_server_thread mesh_server;     /*!< LibLo server thread for the
+                                       *   admin mesh. */
     char *interface_name;             /*!< The name of the network
                                        *   interface for receiving
                                        *   messages. */
@@ -163,11 +170,15 @@ typedef struct _mapper_admin {
                                        *   time syncronization. */
     lo_bundle bundle;                 /*!< Bundle pointer for sending
                                        *   messages on the admin bus. */
+    lo_address bundle_dest;
+    int message_type;
+    mapper_admin_subscriber subscribers; /*!< Linked-list of subscribed peers. */
 } mapper_admin_t;
 
 /*! The handle to this device is a pointer. */
 typedef mapper_admin_t *mapper_admin;
 
+#define ADMIN_TIMEOUT_SEC 10        // timeout after 10 seconds without ping
 
 /**** Router ****/
 
@@ -183,9 +194,9 @@ typedef struct _mapper_connection {
     int calibrating;                        /*!< 1 if the source range is
                                              *   currently being calibrated,
                                              *   0 otherwise. */
-    void *blob;                             /*!< Blob for staging vector
-                                             *   signal updates. */
     mapper_expr expr;                       //!< The mapping expression.
+    mapper_signal_history_t **expr_vars;    //!< User variables values.
+    int num_expr_vars;                      //!< Number of user variables.
     mapper_signal_history_t *history;       /*!< Array of output histories
                                              *   for each signal instance. */
     struct _mapper_connection *next;        //!< Next connection in the list.
@@ -198,12 +209,14 @@ typedef struct _mapper_link_signal {
     struct _mapper_link *link;              //!< The parent link.
     struct _mapper_signal *signal;          //!< The associated signal.
     int num_instances;                      //!< Number of instances allocated.
+//    int max_output_size;                    /*!< Maximum output vector size in
+//                                             *   child connections. */
     mapper_signal_history_t *history;       /*!< Array of value histories
                                              *   for each signal instance. */
     int history_size;                       /*! Size of the history vector. */
     mapper_connection connections;          /*!< The first connection for
                                              *   this signal. */
-    struct _mapper_link_signal *next;     /*!< The next signal connection
+    struct _mapper_link_signal *next;       /*!< The next signal connection
                                              *   in the list. */
 } *mapper_link_signal, *mapper_router_signal, *mapper_receiver_signal;
 
@@ -216,15 +229,17 @@ typedef struct _mapper_queue {
 /*! The link structure is a linked list of links each associated
  *  with a destination address that belong to a controller device. */
 typedef struct _mapper_link {
-    lo_address remote_addr;         //!< Network address of remote endpoint
+    lo_address admin_addr;          //!< Network address of remote endpoint
+    lo_address data_addr;           //!< Network address of remote endpoint
     mapper_db_link_t props;         //!< Properties.
     struct _mapper_device *device;  /*!< The device associated with
                                      *   this link */
     mapper_link_signal signals;     /*!< The list of connections
                                      *  for each signal. */
-    int n_connections;              //!< Number of connections in link.
+    int num_connections;            //!< Number of connections in link.
     mapper_queue queues;            /*!< Linked-list of message queues
                                      *   waiting to be sent. */
+    mapper_sync_clock_t clock;
     struct _mapper_link *next;      //!< Next link in the list.
 } *mapper_link, *mapper_router, *mapper_receiver;
 
@@ -276,6 +291,13 @@ typedef struct _mapper_db {
     fptr_list   link_callbacks;       //<! List of link record callbacks.
 } mapper_db_t, *mapper_db;
 
+typedef struct _mapper_monitor_subscription {
+    char                                *name;
+    int                                 flags;
+    uint32_t                            lease_expiration_sec;
+    struct _mapper_monitor_subscription *next;
+} *mapper_monitor_subscription;
+
 typedef struct _mapper_monitor {
     mapper_admin      admin;    //<! Admin for this monitor.
 
@@ -285,9 +307,15 @@ typedef struct _mapper_monitor {
     int own_admin;
 
     /*! Flags indicating whether information on signals, links,
-     *  and connections should be automatically requested when a
+     *  and connections should be automatically subscribed to when a
      *  new device is seen.*/
-    int autorequest;
+    int autosubscribe;
+
+    /*! The time after which the monitor will declare devices "unresponsive". */
+    int timeout_sec;
+
+    /*! Linked-list of autorenewing device subscriptions. */
+    mapper_monitor_subscription subscriptions;
 
     mapper_db_t       db;       //<! Database for this monitor.
 }  *mapper_monitor;
diff --git a/swig/Makefile.am b/swig/Makefile.am
index 2700006..fcde9bc 100644
--- a/swig/Makefile.am
+++ b/swig/Makefile.am
@@ -1,4 +1,6 @@
 
+_CXX =  $(filter-out ccache,$(CXX))
+
 all-local: _mapper.$(PYEXT)
 
 $(builddir)/%_wrap.c %.py: %.i
@@ -7,7 +9,7 @@ $(builddir)/%_wrap.c %.py: %.i
 
 # Don't interfere with distutils CFLAGS
 _%.$(PYEXT): $(builddir)/%_wrap.c
-	env CFLAGS="" python setup.py build_ext
+	env CFLAGS="" CXX="$(_CXX)" python setup.py build_ext
 	cp -v `./copywhich.sh $@` .
 
 clean-local:
diff --git a/swig/mapper.i b/swig/mapper.i
index c46909b..d6226b7 100644
--- a/swig/mapper.i
+++ b/swig/mapper.i
@@ -1191,13 +1191,19 @@ typedef enum {
     IN_OVERFLOW             = 0x08  //!< No local instances left for incoming remote instance.
 } msig_instance_event_t;
 
-/*! Possible monitor auto-request settings. */
-typedef enum {
-    AUTOREQ_SIGNALS     = 0x01,
-    AUTOREQ_LINKS       = 0x02,
-    AUTOREQ_CONNECTIONS = 0x04,
-    AUTOREQ_ALL         = 0xFF
-} mapper_monitor_autoreq_mode_t;
+/*! Possible monitor auto-subscribe settings. */
+%constant int SUB_NONE                    = 0x00;
+%constant int SUB_DEVICE                  = 0x01;
+%constant int SUB_DEVICE_INPUTS           = 0x02;
+%constant int SUB_DEVICE_OUTPUTS          = 0x04;
+%constant int SUB_DEVICE_SIGNALS          = 0x06; //!< SUB_DEVICE_INPUTS & SUB_DEVICE_OUTPUTS
+%constant int SUB_DEVICE_LINKS_IN         = 0x08;
+%constant int SUB_DEVICE_LINKS_OUT        = 0x10;
+%constant int SUB_DEVICE_LINKS            = 0x18; //!< SUB_DEVICE_LINKS_IN & SUB_DEVICE_LINKS_OUT
+%constant int SUB_DEVICE_CONNECTIONS_IN   = 0x20;
+%constant int SUB_DEVICE_CONNECTIONS_OUT  = 0x40;
+%constant int SUB_DEVICE_CONNECTIONS      = 0x60; //!< SUB_DEVICE_CONNECTIONS_IN & SUB_DEVICE_CONNECTION_OUT
+%constant int SUB_DEVICE_ALL              = 0xFF;
 
 /*! The set of possible actions on a database record, used
  *  to inform callbacks of what is happening to a record. */
@@ -1205,6 +1211,7 @@ typedef enum {
     MDB_MODIFY,
     MDB_NEW,
     MDB_REMOVE,
+    MDB_UNRESPONSIVE,
 } mapper_db_action_t;
 
 typedef enum {
@@ -1432,6 +1439,10 @@ typedef struct _admin {} admin;
     unsigned int get_ordinal() { return mdev_ordinal((mapper_device)$self); }
     int get_num_inputs() { return mdev_num_inputs((mapper_device)$self); }
     int get_num_outputs() { return mdev_num_outputs((mapper_device)$self); }
+    int get_num_links_in() { return mdev_num_links_in((mapper_device)$self); }
+    int get_num_links_out() { return mdev_num_links_out((mapper_device)$self); }
+    int get_num_connections_in() { return mdev_num_connections_in((mapper_device)$self); }
+    int get_num_connections_out() { return mdev_num_connections_out((mapper_device)$self); }
     signal *get_input_by_name(const char *name) {
         return (signal *)mdev_get_input_by_name((mapper_device)$self, name, 0);
     }
@@ -1462,10 +1473,12 @@ typedef struct _admin {} admin;
         mdev_now((mapper_device)$self, &tt);
         return mapper_timetag_get_double(tt);
     }
-    void start_queue(double timetag) {
-        mapper_timetag_t tt;
-        mapper_timetag_set_double(&tt, timetag);
+    double start_queue(double timetag=0) {
+        mapper_timetag_t tt = MAPPER_NOW;
+        if (timetag)
+            mapper_timetag_set_double(&tt, timetag);
         mdev_start_queue((mapper_device)$self, tt);
+        return mapper_timetag_get_double(tt);
     }
     void send_queue(double timetag) {
         mapper_timetag_t tt;
@@ -1500,6 +1513,10 @@ typedef struct _admin {} admin;
         ordinal = property(get_ordinal)
         num_inputs = property(get_num_inputs)
         num_outputs = property(get_num_outputs)
+        num_links_in = property(get_num_links_in)
+        num_links_out = property(get_num_links_out)
+        num_connections_in = property(get_num_connections_in)
+        num_connections_out = property(get_num_connections_out)
         def __propgetter(self):
             device = self
             props = self.get_properties()
@@ -1772,9 +1789,9 @@ typedef struct _admin {} admin;
 }
 
 %extend _monitor {
-    _monitor(admin *DISOWN=0, mapper_monitor_autoreq_mode_t autorequest=0xFF) {
+    _monitor(admin *DISOWN=0, int autosubscribe_flags=0x00) {
         return (monitor *)mapper_monitor_new((mapper_admin) DISOWN,
-                                             autorequest);
+                                             autosubscribe_flags);
     }
     ~_monitor() {
         mapper_monitor_free((mapper_monitor)$self);
@@ -1788,41 +1805,18 @@ typedef struct _admin {} admin;
     db *get_db() {
         return (db *)mapper_monitor_get_db((mapper_monitor)$self);
     }
-    void autorequest(mapper_monitor_autoreq_mode_t autorequest) {
-        mapper_monitor_autorequest((mapper_monitor)$self, autorequest);
-    }
-    int request_devices() {
-        return mapper_monitor_request_devices((mapper_monitor)$self);
-    }
-    int request_device_info(const char* name) {
-        return mapper_monitor_request_device_info((mapper_monitor)$self, name);
+    void autosubscribe(int autosubscribe_flags) {
+        mapper_monitor_autosubscribe((mapper_monitor)$self, autosubscribe_flags);
     }
-    int request_signals_by_device_name(const char* name) {
-        return mapper_monitor_request_signals_by_device_name((mapper_monitor)$self, name);
+    void subscribe(const char *name, int subscribe_flags=0, int timeout=0) {
+        return mapper_monitor_subscribe((mapper_monitor)$self, name,
+                                        subscribe_flags, timeout);
     }
-    int request_input_signals_by_device_name(const char* name) {
-        return mapper_monitor_request_input_signals_by_device_name((mapper_monitor)$self, name);
+    void unsubscribe(const char *name) {
+        return mapper_monitor_unsubscribe((mapper_monitor)$self, name);
     }
-    int request_output_signals_by_device_name(const char* name) {
-        return mapper_monitor_request_output_signals_by_device_name((mapper_monitor)$self, name);
-    }
-    int request_links_by_device_name(const char* name) {
-        return mapper_monitor_request_links_by_device_name((mapper_monitor)$self, name);
-    }
-    int request_links_by_src_device_name(const char* name) {
-        return mapper_monitor_request_links_by_src_device_name((mapper_monitor)$self, name);
-    }
-    int request_links_by_dest_device_name(const char* name) {
-        return mapper_monitor_request_links_by_dest_device_name((mapper_monitor)$self, name);
-    }
-    int request_connections_by_device_name(const char* name) {
-        return mapper_monitor_request_connections_by_device_name((mapper_monitor)$self, name);
-    }
-    int request_connections_by_src_device_name(const char* name) {
-        return mapper_monitor_request_connections_by_src_device_name((mapper_monitor)$self, name);
-    }
-    int request_connections_by_dest_device_name(const char* name) {
-        return mapper_monitor_request_connections_by_dest_device_name((mapper_monitor)$self, name);
+    void request_devices() {
+        mapper_monitor_request_devices((mapper_monitor)$self);
     }
     void link(const char* source_device,
               const char* dest_device,
@@ -1866,6 +1860,9 @@ typedef struct _admin {} admin;
         mapper_monitor_now((mapper_monitor)$self, &tt);
         return mapper_timetag_get_double(tt);
     }
+    void flush(int timeout = ADMIN_TIMEOUT_SEC, int quiet = 1) {
+        mapper_monitor_flush_db((mapper_monitor)$self, timeout, quiet);
+    }
     %pythoncode {
         db = property(get_db)
     }
@@ -2085,4 +2082,7 @@ typedef struct _admin {} admin;
     ~_admin() {
         mapper_admin_free((mapper_admin)$self);
     }
+    const char *libversion() {
+        return mapper_admin_libversion((mapper_admin)$self);
+    }
 }
diff --git a/swig/test.py b/swig/test.py
index 8f34bd6..07ebd4f 100755
--- a/swig/test.py
+++ b/swig/test.py
@@ -62,7 +62,7 @@ def db_cb(rectype, record, action):
     print '  record:',record
     print '  action:',["MODIFY","NEW","REMOVE"][action]
 
-mon = mapper.monitor()
+mon = mapper.monitor(autosubscribe_flags=mapper.SUB_DEVICE)
 
 mon.db.add_device_callback(lambda x,y:db_cb('device',x,y))
 mon.db.add_signal_callback(lambda x,y:db_cb('signal',x,y))
@@ -75,8 +75,6 @@ while not dev.ready():
     dev.poll(10)
     mon.poll()
 
-mon.request_devices()
-
 for i in range(1000):
     dev.poll(10)
     mon.poll()
diff --git a/swig/testcallbacks.py b/swig/testcallbacks.py
index f77cdb9..5409b8b 100755
--- a/swig/testcallbacks.py
+++ b/swig/testcallbacks.py
@@ -37,6 +37,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig.name),
                 '%s%s' %(dest.name, insig.name),
                 {'mode': mapper.MO_REVERSE})
diff --git a/swig/testinstance.py b/swig/testinstance.py
index 6d188f4..2ebb27f 100755
--- a/swig/testinstance.py
+++ b/swig/testinstance.py
@@ -51,6 +51,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig.name),
                 '%s%s' %(dest.name, insig.name),
                 {'mode': mapper.MO_LINEAR})
diff --git a/swig/testquery.py b/swig/testquery.py
index ad86bd8..4a6ec4f 100755
--- a/swig/testquery.py
+++ b/swig/testquery.py
@@ -23,6 +23,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig.name),
                 '%s%s' %(dest.name, insig.name),
                 {'mode': mapper.MO_LINEAR})
diff --git a/swig/testqueue.py b/swig/testqueue.py
index 6f6b129..6d9e989 100755
--- a/swig/testqueue.py
+++ b/swig/testqueue.py
@@ -20,6 +20,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig1.name),
                 '%s%s' %(dest.name, insig1.name),
                 {'mode': mapper.MO_LINEAR})
diff --git a/swig/testreverse.py b/swig/testreverse.py
index 81646e7..1ab5537 100755
--- a/swig/testreverse.py
+++ b/swig/testreverse.py
@@ -23,6 +23,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig.name),
                 '%s%s' %(dest.name, insig.name),
                 {'mode': mapper.MO_REVERSE})
diff --git a/swig/testvector.py b/swig/testvector.py
index fb279e3..040314e 100755
--- a/swig/testvector.py
+++ b/swig/testvector.py
@@ -21,6 +21,9 @@ while not src.ready() or not dest.ready():
 monitor = mapper.monitor()
 
 monitor.link('%s' %src.name, '%s' %dest.name)
+while not src.num_links_out:
+    src.poll()
+    dest.poll(10)
 monitor.connect('%s%s' %(src.name, outsig.name),
                 '%s%s' %(dest.name, insig.name),
                 {'mode': mapper.MO_LINEAR})
diff --git a/test/Makefile.am b/test/Makefile.am
index f0f8ec4..47d27bf 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,5 +1,6 @@
 
-TEST_CFLAGS = -Wall -I$(top_srcdir)/include $(liblo_CFLAGS)
+TEST_CFLAGS = -Wall -I$(top_srcdir)/include @liblo_CFLAGS@
+TEST_CXXFLAGS = -Wall -I$(top_srcdir)/include @liblo_CFLAGS@
 
 if WINDOWS_DLL
 TEST_LDADD = $(top_builddir)/src/*.lo $(liblo_LIBS)
@@ -7,7 +8,7 @@ else
 TEST_LDADD = $(top_builddir)/src/libmapper- at MAJOR_VERSION@.la $(liblo_LIBS)
 endif
 
-noinst_PROGRAMS = test testadmin testcustomtransport testdb testexpression \
+noinst_PROGRAMS = test testadmin testcpp testcustomtransport testdb testexpression \
                   testinstance testlinear testmany testmonitor testparams \
                   testparser testprops testqueue testquery testrate testrecv \
                   testreverse testselect testsend testsignals testspeed testsync \
@@ -16,7 +17,7 @@ noinst_PROGRAMS = test testadmin testcustomtransport testdb testexpression \
 test_all_ordered = testparams testprops testdb testparser testadmin testmany \
                    testsend testrecv test testlinear testexpression testqueue \
                    testquery testrate testinstance testreverse testselect \
-                   testvector testcustomtransport testspeed testsync
+                   testvector testcustomtransport testspeed testsync testcpp
 
 test_CFLAGS = $(TEST_CFLAGS)
 test_SOURCES = test.c
@@ -26,6 +27,10 @@ testadmin_CFLAGS = $(TEST_CFLAGS)
 testadmin_SOURCES = testadmin.c
 testadmin_LDADD = $(TEST_LDADD)
 
+testcpp_CXXFLAGS = $(TEST_CXXFLAGS)
+testcpp_SOURCES = testcpp.cpp
+testcpp_LDADD = $(TEST_LDADD)
+
 testcustomtransport_CFLAGS = $(TEST_CFLAGS)
 testcustomtransport_SOURCES = testcustomtransport.c
 testcustomtransport_LDADD = $(TEST_LDADD)
diff --git a/test/test.c b/test/test.c
index 4ffd8ed..a01d5d1 100644
--- a/test/test.c
+++ b/test/test.c
@@ -149,7 +149,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -169,6 +169,11 @@ void loop()
         mapper_monitor_link(mon, mdev_name(source),
                             mdev_name(destination), 0, 0);
 
+        while (!source->routers) {
+            mdev_poll(source, 10);
+            mdev_poll(destination, 10);
+        }
+
         msig_full_name(sendsig_1, src_name, 1024);
         msig_full_name(recvsig_1, dest_name, 1024);
         mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
@@ -185,15 +190,17 @@ void loop()
         mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
 
         // wait until connection has been established
-        while (!source->routers || !source->routers->n_connections) {
-            mdev_poll(source, 1);
-            mdev_poll(destination, 1);
+        while (!done && !source->routers->num_connections) {
+            mdev_poll(source, 10);
+            mdev_poll(destination, 10);
         }
 
         mapper_monitor_free(mon);
     }
 
+    i = 0;
     float val[3];
+
     while ((!terminate || i < 50) && !done) {
         mdev_poll(source, 0);
         msig_update_double(sendsig_1, ((i % 10) * 1.0f));
diff --git a/test/testcpp.cpp b/test/testcpp.cpp
new file mode 100644
index 0000000..63e986e
--- /dev/null
+++ b/test/testcpp.cpp
@@ -0,0 +1,211 @@
+
+#include <cstring>
+#include <iostream>
+#include <arpa/inet.h>
+#include <cstdio>
+#include <cstdlib>
+#include <array>
+
+#include <mapper/mapper_cpp.h>
+
+#ifdef WIN32
+#define usleep(x) Sleep(x/1000)
+#else
+#include <unistd.h>
+#endif
+
+int received = 0;
+
+void insig_handler(mapper_signal sig, mapper_db_signal props,
+                   int instance_id, void *value, int count,
+                   mapper_timetag_t *timetag)
+{
+    if (value) {
+        printf("--> destination got %s", props->name);
+        switch (props->type) {
+            case 'i': {
+                int *v = (int*)value;
+                for (int i = 0; i < props->length; i++) {
+                    printf(" %d", v[i]);
+                }
+                break;
+            }
+            case 'f': {
+                float *v = (float*)value;
+                for (int i = 0; i < props->length; i++) {
+                    printf(" %f", v[i]);
+                }
+            }
+            case 'd': {
+                double *v = (double*)value;
+                for (int i = 0; i < props->length; i++) {
+                    printf(" %f", v[i]);
+                }
+            }
+            default:
+                break;
+        }
+        printf("\n");
+    }
+    received++;
+}
+
+int main(int argc, char ** argv)
+{
+    int i = 0, result = 0;
+
+    mapper::Device dev("mydevice");
+
+    mapper::Signal sig = dev.add_input("in1", 1, 'f', "meters", 0, 0, 0, 0);
+    dev.remove_input(sig);
+    dev.add_input("in2", 2, 'i', 0, 0, 0, 0, 0);
+    dev.add_input("in3", 2, 'i', 0, 0, 0, 0, 0);
+    dev.add_input("in4", 2, 'i', 0, 0, 0, insig_handler, 0);
+
+    dev.add_output("out1", 1, 'f', "na", 0, 0);
+    dev.remove_output("out1");
+    sig = dev.add_output("out2", 3, 'd', "meters", 0, 0);
+
+    while (!dev.ready()) {
+        dev.poll(100);
+    }
+
+    std::cout << "device " << dev.name() << " ready..." << std::endl;
+    std::cout << "  ordinal: " << dev.ordinal() << std::endl;
+    std::cout << "  id: " << dev.id() << std::endl;
+    std::cout << "  interface: " << dev.interface() << std::endl;
+    const struct in_addr* a = dev.ip4();
+    if (a)
+        std::cout << "  host: " << inet_ntoa(*a) << std::endl;
+    std::cout << "  port: " << dev.port() << std::endl;
+    std::cout << "  num_fds: " << dev.num_fds() << std::endl;
+    std::cout << "  num_inputs: " << dev.num_inputs() << std::endl;
+    std::cout << "  num_outputs: " << dev.num_outputs() << std::endl;
+    std::cout << "  num_links_in: " << dev.num_links_in() << std::endl;
+    std::cout << "  num_links_out: " << dev.num_links_out() << std::endl;
+    std::cout << "  num_connections_in: " << dev.num_connections_in() << std::endl;
+    std::cout << "  num_connections_out: " << dev.num_connections_out() << std::endl;
+
+    // access properties through the db_device
+    dev.properties().get("name").print();
+    std::cout << std::endl;
+
+    int value[] = {1,2,3,4,5,6};
+    dev.properties().set("foo", value, 6);
+    dev.properties().get("foo").print();
+    std::cout << std::endl;
+
+    // can also access properties like this
+    dev.property("name").print();
+    std::cout << std::endl;
+
+    // test std::array<std::string>
+    std::array<std::string, 3> array1 = {{"one", "two", "three"}};
+    dev.property("foo").set(array1);
+    dev.property("foo").print();
+    std::cout << std::endl;
+
+    // test std::array<const char*>
+    std::array<const char*, 3> array2 = {{"four", "five", "six"}};
+    dev.property("foo").set(array2);
+    dev.property("foo").print();
+    std::cout << std::endl;
+
+    // test plain array of const char*
+    const char* array3[3] = {"seven", "eight", "nine"};
+    dev.property("foo").set(array3, 3);
+    dev.property("foo").print();
+    std::cout << std::endl;
+
+    // test std::vector<const char*>
+    const char *array4[3] = {"ten", "eleven", "twelve"};
+    std::vector<const char*> vector1(array4, std::end(array4));
+    dev.property("foo").set(vector1);
+    dev.property("foo").print();
+    std::cout << std::endl;
+
+    // test std::vector<std::string>
+    const char *array5[3] = {"thirteen", "14", "15"};
+    std::vector<std::string> vector2(array5, std::end(array5));
+    dev.property("foo").set(vector2);
+    dev.property("foo").print();
+    std::cout << std::endl;
+
+    mapper::Property p("temp", "tempstring");
+    dev.properties().set(p);
+    dev.property("temp").print();
+    std::cout << std::endl;
+
+    // access property using overloaded index operator
+    dev.properties()["temp"].print();
+    std::cout << std::endl;
+
+    dev.properties().remove("foo");
+    dev.properties().get("foo").print();
+    std::cout << std::endl;
+
+    std::cout << "signal " << sig.full_name() << std::endl;
+
+    for (int i = 0; i < dev.num_inputs(); i++) {
+        std::cout << "input: " << dev.inputs(i).full_name() << std::endl;
+    }
+    mapper::Signal::Iterator iter = dev.inputs().begin();
+    for (; iter != dev.inputs().end(); iter++) {
+        std::cout << "input: " << (*iter).full_name() << std::endl;
+    }
+
+    mapper::Monitor mon(SUB_DEVICE_ALL);
+    mon.link(dev.name(), dev.name());
+    while (dev.num_links_in() <= 0) { dev.poll(100); }
+
+    mapper::Db::Connection c;
+    c.set_mode(MO_EXPRESSION);
+    c.set_expression("y=x[0:1]+123");
+    double d[3] = {1., 2., 3.};
+    c.set_src_min(mapper::Property(0, d, 3));
+    mon.connect("/mydevice.1/out2", "/mydevice.1/in4", c);
+    while (dev.num_connections_in() <= 0) { dev.poll(100); }
+
+    std::vector <double> v(3);
+    while (i++ < 100) {
+        dev.poll(10);
+        mon.poll();
+        v[i%3] = i;
+        sig.update(v);
+    }
+
+    // check db records 
+    std::cout << "db records:" << std::endl;
+    for (auto const &device : mon.db().devices()) {
+        std::cout << "  device: ";
+        device.get("name").print();
+        std::cout << std::endl;
+    }
+    for (auto const &signal : mon.db().inputs()) {
+        std::cout << "  input signal: ";
+        signal.get("name").print();
+        std::cout << std::endl;
+    }
+    for (auto const &signal : mon.db().outputs()) {
+        std::cout << "  output signal: ";
+        signal.get("name").print();
+        std::cout << std::endl;
+    }
+    for (auto const &link : mon.db().links()) {
+        std::cout << "  link: ";
+        link.get("src_name").print();
+        std::cout << " -> ";
+        link.get("dest_name").print();
+        std::cout << std::endl;
+    }
+    for (auto const &conn : mon.db().connections()) {
+        std::cout << "  connection: ";
+        conn.get("src_name").print();
+        std::cout << " -> ";
+        conn.get("dest_name").print();
+        std::cout << std::endl;
+    }
+
+    printf("Test %s.\n", result ? "FAILED" : "PASSED");
+    return result;
+}
diff --git a/test/testcustomtransport.c b/test/testcustomtransport.c
index ee35aaa..654c06e 100644
--- a/test/testcustomtransport.c
+++ b/test/testcustomtransport.c
@@ -249,7 +249,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -269,6 +269,11 @@ void loop()
         mapper_monitor_link(mon, mdev_name(source),
                             mdev_name(destination), 0, 0);
 
+        while (i++ < 10) {
+            mdev_poll(source, 0);
+            mdev_poll(destination, 0);
+        }
+
         msig_full_name(sendsig, src_name, 1024);
         msig_full_name(recvsig, dest_name, 1024);
         mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
diff --git a/test/testdb.c b/test/testdb.c
index 150dc81..fc40afd 100644
--- a/test/testdb.c
+++ b/test/testdb.c
@@ -104,10 +104,10 @@ int main(int argc, char **argv)
         goto done;
     }
 
-    mapper_db_add_or_update_device_params(db, "/testdb.1", &msg);
-    mapper_db_add_or_update_device_params(db, "/testdb__.2", &msg);
-    mapper_db_add_or_update_device_params(db, "/testdb.3", &msg);
-    mapper_db_add_or_update_device_params(db, "/testdb__.4", &msg);
+    mapper_db_add_or_update_device_params(db, "/testdb.1", &msg, 0);
+    mapper_db_add_or_update_device_params(db, "/testdb__.2", &msg, 0);
+    mapper_db_add_or_update_device_params(db, "/testdb.3", &msg, 0);
+    mapper_db_add_or_update_device_params(db, "/testdb__.4", &msg, 0);
 
     args[0] = (lo_arg*)"@direction";
     args[1] = (lo_arg*)"input";
diff --git a/test/testexpression.c b/test/testexpression.c
index 6850b50..af66e36 100644
--- a/test/testexpression.c
+++ b/test/testexpression.c
@@ -111,6 +111,11 @@ int setup_connection()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_db_connection_t props;
@@ -120,9 +125,9 @@ int setup_connection()
                            CONNECTION_MODE | CONNECTION_EXPRESSION);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
     }
 
     mapper_monitor_free(mon);
@@ -132,7 +137,7 @@ int setup_connection()
 
 void wait_ready()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
         usleep(500 * 1000);
diff --git a/test/testinstance.c b/test/testinstance.c
index 2de186b..37e6621 100644
--- a/test/testinstance.c
+++ b/test/testinstance.c
@@ -38,17 +38,16 @@ int setup_source()
     source = mdev_new("testInstanceSend", 0, 0);
     if (!source)
         goto error;
-    eprintf("source created.\n");
 
     float mn=0, mx=10;
 
-    sendsig = mdev_add_output(source, "/outsig", 1, 'f', 0, &mn, &mx);
+    sendsig = mdev_add_poly_output(source, "/outsig", 1, 'f', 0, &mn, &mx, 10);
     if (!sendsig)
         goto error;
-    msig_reserve_instances(sendsig, 9, 0, 0);
 
-    eprintf("Output signal registered.\n");
-    eprintf("Number of outputs: %d\n", mdev_num_outputs(source));
+    eprintf("Output signal added with %i instances.\n",
+              msig_num_active_instances(sendsig)
+            + msig_num_reserved_instances(sendsig));
 
     return 0;
 
@@ -102,23 +101,23 @@ int setup_destination()
     destination = mdev_new("testInstanceRecv", 0, 0);
     if (!destination)
         goto error;
-    eprintf("destination created.\n");
 
     float mn=0;//, mx=1;
 
-    recvsig = mdev_add_input(destination, "/insig", 1, 'f',
-                             0, &mn, 0, insig_handler, 0);
+    // Specify 0 instances since we wich to use specific ids
+    recvsig = mdev_add_poly_input(destination, "/insig", 1, 'f',
+                                  0, &mn, 0, 0, insig_handler, 0);
     if (!recvsig)
         goto error;
 
-    // remove the default instance "0"
-    msig_remove_instance(recvsig, 0);
     int i;
     for (i=100; i<104; i++) {
         msig_reserve_instances(recvsig, 1, &i, 0);
     }
 
-    eprintf("Input signal registered.\n");
+    eprintf("Input signal added with %i instances.\n",
+              msig_num_active_instances(recvsig)
+            + msig_num_reserved_instances(recvsig));
     eprintf("Number of inputs: %d\n", mdev_num_inputs(destination));
 
     return 0;
@@ -139,7 +138,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -164,16 +163,24 @@ void connect_signals()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
-    mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
+    mapper_db_connection_t props;
+    props.expression = "foo=1;  y=y{-1}+foo";
+    props.mode = MO_BYPASS;
+    mapper_monitor_connect(mon, src_name, dest_name, &props,
+                           CONNECTION_MODE | CONNECTION_EXPRESSION);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
     }
-
     mapper_monitor_free(mon);
 }
 
diff --git a/test/testlinear.c b/test/testlinear.c
index d8c06d3..31ddd84 100644
--- a/test/testlinear.c
+++ b/test/testlinear.c
@@ -108,6 +108,12 @@ int setup_connection()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    // Wait until link has been established
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
 
@@ -128,11 +134,10 @@ int setup_connection()
                            CONNECTION_SRC_TYPE | CONNECTION_SRC_LENGTH |
                            CONNECTION_DEST_TYPE | CONNECTION_DEST_LENGTH);
 
-    // poll devices for a bit to allow time for connection
-    int i = 0;
-    while (i++ < 10) {
-        mdev_poll(destination, 10);
+    // Wait until connection has been established
+    while (!done && !source->routers->num_connections) {
         mdev_poll(source, 10);
+        mdev_poll(destination, 10);
     }
 
     mapper_monitor_free(mon);
@@ -142,7 +147,7 @@ int setup_connection()
 
 void wait_ready()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
         usleep(500 * 1000);
diff --git a/test/testmonitor.c b/test/testmonitor.c
index 286f7f5..b63392a 100644
--- a/test/testmonitor.c
+++ b/test/testmonitor.c
@@ -178,7 +178,7 @@ void printconnection(mapper_db_connection con)
 /*! Creation of a local dummy device. */
 int setup_monitor()
 {
-    mon = mapper_monitor_new(0, AUTOREQ_ALL);
+    mon = mapper_monitor_new(0, SUB_DEVICE_ALL);
     if (!mon)
         goto error;
     printf("Monitor created.\n");
@@ -274,6 +274,10 @@ void on_device(mapper_db_device dev, mapper_db_action_t a, void *user)
     case MDB_REMOVE:
         printf("removed.\n");
         break;
+    case MDB_UNRESPONSIVE:
+        printf("unresponsive.\n");
+        mapper_monitor_flush_db(mon, 10, 0);
+        break;
     }
     dbpause();
     update = 1;
@@ -292,6 +296,9 @@ void on_signal(mapper_db_signal sig, mapper_db_action_t a, void *user)
     case MDB_REMOVE:
         printf("removed.\n");
         break;
+    case MDB_UNRESPONSIVE:
+        printf("unresponsive.\n");
+        break;
     }
     dbpause();
     update = 1;
@@ -299,7 +306,7 @@ void on_signal(mapper_db_signal sig, mapper_db_action_t a, void *user)
 
 void on_connection(mapper_db_connection con, mapper_db_action_t a, void *user)
 {
-    printf("Connecting %s -> %s ", con->src_name, con->dest_name);
+    printf("Connection %s -> %s ", con->src_name, con->dest_name);
     switch (a) {
     case MDB_NEW:
         printf("added.\n");
@@ -310,6 +317,9 @@ void on_connection(mapper_db_connection con, mapper_db_action_t a, void *user)
     case MDB_REMOVE:
         printf("removed.\n");
         break;
+    case MDB_UNRESPONSIVE:
+        printf("unresponsive.\n");
+        break;
     }
     dbpause();
     update = 1;
@@ -328,6 +338,9 @@ void on_link(mapper_db_link lnk, mapper_db_action_t a, void *user)
     case MDB_REMOVE:
         printf("removed.\n");
         break;
+    case MDB_UNRESPONSIVE:
+        printf("unresponsive.\n");
+        break;
     }
     dbpause();
     update = 1;
@@ -381,8 +394,6 @@ int main(int argc, char **argv)
     mapper_db_add_connection_callback(db, on_connection, 0);
     mapper_db_add_link_callback(db, on_link, 0);
 
-    mapper_monitor_request_devices(mon);
-
     loop();
 
   done:
diff --git a/test/testparser.c b/test/testparser.c
index 0df1143..35e8c13 100644
--- a/test/testparser.c
+++ b/test/testparser.c
@@ -7,6 +7,9 @@
 #include <sys/time.h>
 #include <string.h>
 
+#define DEST_ARRAY_LEN 6
+#define MAX_VARS 3
+
 #define eprintf(format, ...) do {               \
     if (verbose)                                \
         fprintf(stdout, format, ##__VA_ARGS__); \
@@ -17,16 +20,19 @@ char str[256];
 mapper_expr e;
 int result = 0;
 int iterations = 1000000;
+int token_count = 0;
 
-int src_int[] = {1, 2, 3}, dest_int[3];
-float src_float[] = {1.0f, 2.0f, 3.0f}, dest_float[3];
-double src_double[] = {1.0, 2.0, 3.0}, dest_double[3];
+int src_int[] = {1, 2, 3}, dest_int[DEST_ARRAY_LEN];
+float src_float[] = {1.0f, 2.0f, 3.0f}, dest_float[DEST_ARRAY_LEN];
+double src_double[] = {1.0, 2.0, 3.0}, dest_double[DEST_ARRAY_LEN];
 double then, now;
+double total_elapsed_time = 0;
+char typestring[3];
 
 mapper_timetag_t tt_in = {0, 0}, tt_out = {0, 0};
 
 // signal_history structures
-mapper_signal_history_t inh, outh;
+mapper_signal_history_t inh, outh, user_vars[MAX_VARS], *user_vars_p;
 
 /*! Internal function to get the current time. */
 static double get_current_time()
@@ -36,14 +42,80 @@ static double get_current_time()
     return (double) tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
-/* Examples:
- unit-delay indexing
- vector length mismatches
+typedef struct _variable {
+    char *name;
+    int vector_index;
+    int vector_length;
+    char datatype;
+    char casttype;
+    char history_size;
+    char vector_length_locked;
+    char assigned;
+} mapper_variable_t, *mapper_variable;
+
+struct _mapper_expr
+{
+    void *tokens;
+    void *start;
+    int length;
+    int vector_size;
+    int input_history_size;
+    int output_history_size;
+    mapper_variable variables;
+    int num_variables;
+    int constant_output;
+};
+
+/* TODO:
  multiplication by 0
  addition/subtraction of 0
- division by 0?
+ division by 0
  */
 
+void print_value(char *types, int length, const void *value, int position)
+{
+    if (!value || !types || length < 1)
+        return;
+
+    if (length > 1)
+        printf("[");
+
+    int i, offset = position * length;
+    for (i = 0; i < length; i++) {
+        switch (types[i]) {
+            case 'N':
+                printf("NULL, ");
+                break;
+            case 'f':
+            {
+                float *pf = (float*)value;
+                printf("%g, ", pf[i + offset]);
+                break;
+            }
+            case 'i':
+            {
+                int *pi = (int*)value;
+                printf("%d, ", pi[i + offset]);
+                break;
+            }
+            case 'd':
+            {
+                double *pd = (double*)value;
+                printf("%g, ", pd[i + offset]);
+                break;
+            }
+            default:
+                printf("\nTYPE ERROR\n");
+                return;
+        }
+    }
+
+    if (length > 1)
+        printf("\b\b]");
+    else
+        printf("\b\b");
+}
+
 void setup_test(char in_type, int in_size, int in_length, void *in_value,
                 char out_type, int out_size, int out_length, void *out_value)
 {
@@ -62,46 +134,82 @@ void setup_test(char in_type, int in_size, int in_length, void *in_value,
     outh.timetag = &tt_out;
 }
 
-int parse_and_eval()
+#define EXPECT_SUCCESS 0
+#define EXPECT_FAILURE 1
+
+int parse_and_eval(int expectation)
 {
+    // clear output arrays
+    int i;
+    for (i = 0; i < DEST_ARRAY_LEN; i++) {
+        dest_int[i] = 0;
+        dest_float[i] = 0.0f;
+        dest_double[i] = 0.0;
+    }
+
     eprintf("**********************************\n");
     eprintf("Parsing string '%s'\n", str);
     if (!(e = mapper_expr_new_from_string(str, inh.type, outh.type,
-                                          inh.length, outh.length,
-                                          &inh.size, &outh.size))) {
+                                          inh.length, outh.length))) {
         eprintf("Parser FAILED.\n");
-        return 1;
+        goto fail;
+    }
+    inh.size = mapper_expr_input_history_size(e);
+    outh.size = mapper_expr_output_history_size(e);
+
+    if (mapper_expr_num_variables(e) > MAX_VARS) {
+        eprintf("Maximum variables exceeded.\n");
+        goto fail;
     }
 
+    // reallocate variable value histories
+    for (i = 0; i < e->num_variables; i++) {
+        eprintf("user_var[%d]: %p\n", i, &user_vars[i]);
+        mhist_realloc(&user_vars[i], e->variables[i].history_size,
+                      sizeof(double), 0);
+    }
+    user_vars_p = user_vars;
+
 #ifdef DEBUG
-    if (verbose)
-        printexpr("Parser returned:", e);
+    if (verbose) {
+        char str[128];
+        snprintf(str, 128, "Parser returned %d tokens:", e->length);
+        printexpr(str, e);
+    }
 #endif
 
-    if (!mapper_expr_evaluate(e, &inh, &outh)) {
+    token_count += e->length;
+
+    eprintf("Try evaluation once...\n");
+    if (!mapper_expr_evaluate(e, &inh, &user_vars_p, &outh, typestring)) {
         eprintf("Evaluation FAILED.\n");
-        return 1;
+        goto fail;
     }
 
     then = get_current_time();
     eprintf("Calculate expression %i times... ", iterations);
-    int i = iterations;
+    i = iterations-1;
     while (i--) {
-        mapper_expr_evaluate(e, &inh, &outh);
+        mapper_expr_evaluate(e, &inh, &user_vars_p, &outh, typestring);
     }
     now = get_current_time();
-    eprintf("%f seconds.\n", now-then);
+    eprintf("%g seconds.\n", now-then);
+    total_elapsed_time += now-then;
 
-    eprintf("Got:      ");
-    if (verbose)
-        mapper_prop_pp(outh.type, outh.length, outh.value);
-    eprintf(" \n");
+    if (verbose) {
+        printf("Got:      ");
+        print_value(typestring, outh.length, outh.value, outh.position);
+        printf(" \n");
+    }
+    else
+        printf(".");
 
     mapper_expr_free(e);
 
-    if (!verbose)
-        printf(".");
-    return 0;
+    return expectation != EXPECT_SUCCESS;
+
+fail:
+    return expectation != EXPECT_FAILURE;
 }
 
 int run_tests()
@@ -111,148 +219,266 @@ int run_tests()
     /* Complex string */
     snprintf(str, 256, "y=26*2/2+log10(pi)+2.*pow(2,1*(3+7*.1)*1.1+x{0}[0])*3*4+cos(2.)");
     setup_test('f', 1, 1, src_float, 'f', 1, 1, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: %f\n", 26*2/2+log10f(M_PI)+2.f*powf(2,1*(3+7*.1f)*1.1f+src_float[0])*3*4+cosf(2.0f));
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: %g\n", 26*2/2+log10f(M_PI)+2.f
+            *powf(2,1*(3+7*.1f)*1.1f+src_float[0])*3*4+cosf(2.0f));
 
     /* Building vectors, conditionals */
     snprintf(str, 256, "y=(x>1)?[1,2,3]:[2,4,6]");
     setup_test('f', 1, 3, src_float, 'i', 1, 3, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: [%i, %i, %i]\n", src_float[0]>1?1:2, src_float[1]>1?2:4,
            src_float[2]>1?3:6);
 
     /* Conditionals with shortened syntax */
     snprintf(str, 256, "y=x?:123");
     setup_test('f', 1, 1, src_float, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n", (int)src_float[0]?:123);
 
+    /* Conditional that should be optimized */
+    snprintf(str, 256, "y=1?2:123");
+    setup_test('f', 1, 1, src_float, 'i', 1, 1, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: 2\n");
+
     /* Building vectors with variables, operations inside vector-builder */
     snprintf(str, 256, "y=[x*-2+1,0]");
     setup_test('i', 1, 2, src_int, 'd', 1, 3, dest_double);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f, %f]\n", (double)src_int[0]*-2+1,
-           (double)src_int[1]*-2+1, 0.0);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g, %g]\n", (double)src_int[0]*-2+1,
+            (double)src_int[1]*-2+1, 0.0);
 
     /* Building vectors with variables, operations inside vector-builder */
     snprintf(str, 256, "y=[-99.4, -x*1.1+x]");
     setup_test('i', 1, 2, src_int, 'd', 1, 3, dest_double);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f, %f]\n", -99.4,
-           (double)(-src_int[0]*1.1+src_int[0]),
-           (double)(-src_int[1]*1.1+src_int[1]));
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g, %g]\n", -99.4,
+            (double)(-src_int[0]*1.1+src_int[0]),
+            (double)(-src_int[1]*1.1+src_int[1]));
 
     /* Indexing vectors by range */
     snprintf(str, 256, "y=x[1:2]+100");
     setup_test('d', 1, 3, src_double, 'f', 1, 2, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f]\n", (float)src_double[1]+100,
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g]\n", (float)src_double[1]+100,
            (float)src_double[2]+100);
 
     /* Typical linear scaling expression with vectors */
     snprintf(str, 256, "y=x*[0.1,3.7,-.1112]+[2,1.3,9000]");
     setup_test('f', 1, 3, src_float, 'f', 1, 3, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f, %f]\n", src_float[0]*0.1f+2.f,
-           src_float[1]*3.7f+1.3f, src_float[2]*-.1112f+9000.f);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g, %g]\n", src_float[0]*0.1f+2.f,
+            src_float[1]*3.7f+1.3f, src_float[2]*-.1112f+9000.f);
 
     /* Check type and vector length promotion of operation sequences */
     snprintf(str, 256, "y=1+2*3-4*x");
     setup_test('f', 1, 2, src_float, 'f', 1, 2, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f]\n", 1.f+2.f*3.f-4.f*src_float[0],
-           1.f+2.f*3.f-4.f*src_float[1]);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g]\n", 1.f+2.f*3.f-4.f*src_float[0],
+            1.f+2.f*3.f-4.f*src_float[1]);
 
     /* Swizzling, more pre-computation */
     snprintf(str, 256, "y=[x[2],x[0]]*0+1+12");
     setup_test('f', 1, 3, src_float, 'f', 1, 2, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: [%f, %f]\n", src_float[2]*0.f+1.f+12.f,
-           src_float[0]*0.f+1.f+12.f);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, %g]\n", src_float[2]*0.f+1.f+12.f,
+            src_float[0]*0.f+1.f+12.f);
 
     /* Logical negation */
     snprintf(str, 256, "y=!(x[1]*0)");
     setup_test('d', 1, 3, src_double, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n", (int)!(src_double[1]*0));
 
     /* any() */
     snprintf(str, 256, "y=any(x-1)");
     setup_test('d', 1, 3, src_double, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n", ((int)src_double[0]-1)?1:0
            | ((int)src_double[1]-1)?1:0
            | ((int)src_double[2]-1)?1:0);
 
     /* all() */
-    snprintf(str, 256, "y=x[1:2]*all(x-1)");
-    setup_test('d', 1, 3, src_double, 'i', 1, 2, dest_int);
-    result += parse_and_eval();
+    snprintf(str, 256, "y=x[2]*all(x-1)");
+    setup_test('d', 1, 3, src_double, 'i', 1, 1, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
     int temp = ((int)src_double[0]-1)?1:0 & ((int)src_double[1]-1)?1:0
                 & ((int)src_double[2]-1)?1:0;
-    eprintf("Expected: [%i, %i]\n", (int)src_double[1] * temp,
-           (int)src_double[2] * temp);
+    eprintf("Expected: %i\n", (int)src_double[2] * temp);
 
     /* pi and e, extra spaces */
     snprintf(str, 256, "y=x + pi -     e");
     setup_test('d', 1, 1, src_double, 'f', 1, 1, dest_float);
-    result += parse_and_eval();
-    eprintf("Expected: %f\n", (float)(src_double[0]+M_PI-M_E));
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: %g\n", (float)(src_double[0]+M_PI-M_E));
 
     /* bad vector notation */
     snprintf(str, 256, "y=(x-2)[1]");
     setup_test('i', 1, 1, src_int, 'i', 1, 1, dest_int);
-    result += !parse_and_eval();
+    result += parse_and_eval(EXPECT_FAILURE);
     eprintf("Expected: FAILURE\n");
 
     /* vector index outside bounds */
     snprintf(str, 256, "y=x[3]");
     setup_test('i', 1, 3, src_int, 'i', 1, 1, dest_int);
-    result += !parse_and_eval();
+    result += parse_and_eval(EXPECT_FAILURE);
     eprintf("Expected: FAILURE\n");
 
     /* vector length mismatch */
     snprintf(str, 256, "y=x[1:2]");
     setup_test('i', 1, 3, src_int, 'i', 1, 1, dest_int);
-    result += !parse_and_eval();
+    result += parse_and_eval(EXPECT_FAILURE);
     eprintf("Expected: FAILURE\n");
 
     /* unnecessary vector notation */
     snprintf(str, 256, "y=x+[1]");
     setup_test('i', 1, 1, src_int, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n", src_int[0]+1);
 
     /* invalid history index */
     snprintf(str, 256, "y=x{-101}");
     setup_test('i', 1, 1, src_int, 'i', 1, 1, dest_int);
-    result += !parse_and_eval();
+    result += parse_and_eval(EXPECT_FAILURE);
     eprintf("Expected: FAILURE\n");
 
     /* invalid history index */
     snprintf(str, 256, "y=x-y{-101}");
     setup_test('i', 1, 1, src_int, 'i', 1, 1, dest_int);
-    result += !parse_and_eval();
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+
+    /* scientific notation */
+    snprintf(str, 256, "y=x[1]*1.23e-20");
+    setup_test('i', 1, 2, src_int, 'd', 1, 1, dest_double);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: %g\n", (double)src_int[1] * 1.23e-20);
+
+    /* Vector assignment */
+    snprintf(str, 256, "y[1]=x[1]");
+    setup_test('d', 1, 3, src_double, 'i', 1, 3, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [NULL, %i, NULL]\n", (int)src_double[1]);
+
+    /* Vector assignment */
+    snprintf(str, 256, "y[1:2]=[x[1],10]");
+    setup_test('d', 1, 3, src_double, 'i', 1, 3, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [NULL, %i, %i]\n", (int)src_double[1], 10);
+
+    /* Output vector swizzling */
+    snprintf(str, 256, "[y[0],y[2]]=x[1:2]");
+    setup_test('f', 1, 3, src_float, 'd', 1, 3, dest_double);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%g, NULL, %g]\n", (double)src_float[1],
+            (double)src_float[2]);
+
+    /* Multiple expressions */
+    snprintf(str, 256, "y[0]=x*100-23.5; y[2]=100-x*6.7");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+
+    /* Error check: separating sub-expressions with commas */
+    snprintf(str, 256, "foo=1,  y=y{-1}+foo");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
     eprintf("Expected: FAILURE\n");
 
     /* Initialize filters */
-//    snprintf(str, 256, "y=x+y{-1}, y{-1}=100");
-//    setup_test('i', 1, 1, src_int, 'i', 1, 1, dest_int);
-//    result += parse_and_eval();
-//    eprintf("Expected: %i\n", src_int[0]*iterations + 100);
+    snprintf(str, 256, "y=x+y{-1}; y{-1}=100");
+    setup_test('i', 1, 1, src_int, 'i', 2, 1, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: %i\n", src_int[0]*iterations + 100);
+
+    /* Initialize filters + vector index */
+    snprintf(str, 256, "y=x+y{-1}; y[1]{-1}=100");
+    setup_test('i', 1, 2, src_int, 'i', 2, 2, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%i, %i]\n", src_int[0]*iterations,
+            src_int[1]*iterations + 100);
+
+    /* Initialize filters + vector index */
+    snprintf(str, 256, "y=x+y{-1}; y{-1}=[100,101]");
+    setup_test('i', 1, 2, src_int, 'i', 2, 2, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%i, %i]\n", src_int[0]*iterations + 100,
+            src_int[1]*iterations + 101);
+
+    /* Initialize filters */
+    snprintf(str, 256, "y=x+y{-1}; y[0]{-1}=100; y[2]{-1}=200");
+    setup_test('i', 1, 3, src_int, 'i', 2, 3, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [%i, %i, %i]\n", src_int[0]*iterations + 100,
+            src_int[1]*iterations, src_int[2]*iterations + 200);
+
+    /* Initialize filters */
+    snprintf(str, 256, "y=x+y{-1}-y{-2}; y{-1}=[100,101]; y{-2}=[100,101]");
+    setup_test('i', 1, 2, src_int, 'i', 3, 2, dest_int);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: [1, 2]\n");
+
+    /* Only initialize */
+    snprintf(str, 256, "y{-1}=100");
+    setup_test('i', 1, 3, src_int, 'i', 1, 1, dest_int);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+
+    /* Some examples of bad syntax */
+    snprintf(str, 256, " ");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+    snprintf(str, 256, " ");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+    snprintf(str, 256, "y");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+    snprintf(str, 256, "y=");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+    snprintf(str, 256, "=x");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
+    snprintf(str, 256, "sin(x)");
+    setup_test('i', 1, 1, src_int, 'f', 1, 3, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
 
-    /* TODO: scientific notation */
+    /* Variable declaration */
+    snprintf(str, 256, "var=3.5; y=x+var");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: %g\n", (float)src_int[0] + 3.5);
+
+    /* Variable declaration */
+    snprintf(str, 256, "ema=ema{-1}*0.9+x*0.1; y=ema*2; ema{-1}=90");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: 2\n");
+
+    /* Malformed variable declaration */
+    snprintf(str, 256, "y=x + myvariable * 10");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
 
     /* Vector functions mean() and sum() */
     snprintf(str, 256, "y=mean(x)==(sum(x)/3)");
     setup_test('f', 1, 3, src_float, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n", 1);
 
-    /* Vector function vmax() and vmin() */
-    snprintf(str, 256, "y=vmax(x)-vmin(x)");
+    /* Overloaded vector functions max() and min() */
+    snprintf(str, 256, "y=max(x)-min(x)");
     setup_test('f', 1, 3, src_float, 'i', 1, 1, dest_int);
-    result += parse_and_eval();
+    result += parse_and_eval(EXPECT_SUCCESS);
     eprintf("Expected: %i\n",
             ((src_float[0]>src_float[1])?
              (src_float[0]>src_float[2]?(int)src_float[0]:(int)src_float[2]):
@@ -261,8 +487,23 @@ int run_tests()
              (src_float[0]<src_float[2]?(int)src_float[0]:(int)src_float[2]):
              (src_float[1]<src_float[2]?(int)src_float[1]:(int)src_float[2])));
 
-    printf("**********************************\n");
-    printf("Failed %d tests\n", result);
+    /* Optimization: operations by zero */
+    snprintf(str, 256, "y=0*sin(x)*200+1.1");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: 1.1\n");
+
+    /* Optimization: operations by one */
+    snprintf(str, 256, "y=x*1");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_SUCCESS);
+    eprintf("Expected: 1\n");
+
+    /* Error check: division by zero */
+    snprintf(str, 256, "y=x/0");
+    setup_test('i', 1, 1, src_int, 'f', 1, 1, dest_float);
+    result += parse_and_eval(EXPECT_FAILURE);
+    eprintf("Expected: FAILURE\n");
 
     return result;
 }
@@ -279,12 +520,18 @@ int main(int argc, char **argv)
                     case 'h':
                         eprintf("testparser.c: possible arguments "
                                 "-q quiet (suppress output), "
-                                "-h help\n");
+                                "-h help, "
+                                "--num_iterations <int>\n");
                         return 1;
                         break;
                     case 'q':
                         verbose = 0;
                         break;
+                    case '-':
+                        if (++j < len && strcmp(argv[i]+j, "num_iterations")==0)
+                            if (++i < argc)
+                                iterations = atoi(argv[i]);
+                        break;
                     default:
                         break;
                 }
@@ -293,5 +540,11 @@ int main(int argc, char **argv)
     }
 
     result = run_tests();
-    printf("Test %s.\n", result ? "FAILED" : "PASSED");
+    eprintf("**********************************\n");
+    printf("Test %s ", result ? "FAILED" : "PASSED");
+    if (!result)
+        printf("in %f seconds using %d tokens in total.\n",
+               total_elapsed_time, token_count);
+    else
+        printf("\n");
 }
diff --git a/test/testprops.c b/test/testprops.c
index cb1dfaf..73d0bb3 100644
--- a/test/testprops.c
+++ b/test/testprops.c
@@ -93,7 +93,7 @@ int main(int argc, char **argv)
         }
     }
 
-    mapper_signal sig = msig_new("/test", 1, 'f', 1, "Hz", 0, 0, 0, 0);
+    mapper_signal sig = msig_new("/test", 1, 'f', 1, "Hz", 0, 0, -1, 0, 0);
     mapper_db_signal sigprop = msig_properties(sig);
 
     /* Test that default parameters are all listed. */
diff --git a/test/testquery.c b/test/testquery.c
index b146a73..8fcbc8b 100644
--- a/test/testquery.c
+++ b/test/testquery.c
@@ -33,8 +33,12 @@ void query_response_handler(mapper_signal sig, mapper_db_signal props,
                             int instance_id, void *value, int count,
                             mapper_timetag_t *timetag)
 {
+    int i;
     if (value) {
-        eprintf("--> source got query response: %s %i\n", props->name, (*(int*)value));
+        eprintf("--> source got query response: %s ", props->name);
+        for (i = 0; i < props->length * count; i++)
+            eprintf("%i ", ((int*)value)[i]);
+        eprintf("\n");
     }
     else {
         eprintf("--> source got empty query response: %s\n", props->name);
@@ -52,12 +56,13 @@ int setup_source()
         goto error;
     eprintf("source created.\n");
 
-    int mn=0, mx=10;
+    int mn[]={0,0,0,0}, mx[]={10,10,10,10};
 
     for (int i = 0; i < 4; i++) {
         snprintf(sig_name, 20, "%s%i", "/outsig_", i);
-        sendsig[i] = mdev_add_output(source, sig_name, 1, 'i', 0, &mn, &mx);
+        sendsig[i] = mdev_add_output(source, sig_name, i+1, 'i', 0, mn, mx);
         msig_set_callback(sendsig[i], query_response_handler, 0);
+        msig_update(sendsig[i], mn, 0, MAPPER_NOW);
     }
 
     eprintf("Output signals registered.\n");
@@ -104,12 +109,12 @@ int setup_destination()
         goto error;
     eprintf("destination created.\n");
 
-    float mn=0, mx=1;
+    float mn[]={0,0,0,0}, mx[]={1,1,1,1};
 
     for (int i = 0; i < 4; i++) {
         snprintf(sig_name, 10, "%s%i", "/insig_", i);
-        recvsig[i] = mdev_add_input(destination, sig_name, 1,
-                                    'f', 0, &mn, &mx, insig_handler, 0);
+        recvsig[i] = mdev_add_input(destination, sig_name, i+1,
+                                    'f', 0, mn, mx, insig_handler, 0);
     }
 
     eprintf("Input signal /insig registered.\n");
@@ -133,7 +138,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -150,15 +155,31 @@ int setup_connections()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
-    for (i = 0; i < 4; i++) {
+    i = 0;
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+        if (i++ > 100)
+            return 1;
+    }
+
+    for (int i = 0; i < 4; i++) {
         msig_full_name(sendsig[i], src_name, 1024);
         msig_full_name(recvsig[i], dest_name, 1024);
         mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
     }
 
-    // wait until connection has been established
+    // swap the last two signals to mix up signal vector lengths
+    msig_full_name(sendsig[2], src_name, 1024);
+    msig_full_name(recvsig[3], dest_name, 1024);
+    mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
+    msig_full_name(sendsig[3], src_name, 1024);
+    msig_full_name(recvsig[2], dest_name, 1024);
+    mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
+
     i = 0;
-    while (!source->routers || !source->routers->n_connections) {
+    // wait until connection has been established
+    while (!done && !source->routers->num_connections) {
         mdev_poll(source, 10);
         mdev_poll(destination, 10);
         if (i++ > 100)
@@ -172,17 +193,19 @@ int setup_connections()
 void loop()
 {
     eprintf("-------------------- GO ! --------------------\n");
-    int i = 0, j = 0, count;
+    int i = 10, j = 0, count;
+    float value[] = {0., 0., 0., 0.};
 
     while ((!terminate || i < 50) && !done) {
-        for (j = 0; j < 2; j++) {
-            msig_update_float(recvsig[j], ((i % 10) * 1.0f));
+        for (j = 0; j < 4; j++)
+            value[j] = (i % 10) * 1.0f;
+        for (j = 0; j < 4; j++) {
+            msig_update(recvsig[j], value, 0, MAPPER_NOW);
         }
         eprintf("\ndestination values updated to %f -->\n", (i % 10) * 1.0f);
         for (j = 0; j < 4; j++) {
-            count = msig_query_remotes(sendsig[j], MAPPER_NOW);
+            sent += count = msig_query_remotes(sendsig[j], MAPPER_NOW);
             eprintf("Sent %i queries for sendsig[%i]\n", count, j);
-            sent += count;
         }
         mdev_poll(destination, 50);
         mdev_poll(source, 50);
@@ -255,8 +278,8 @@ int main(int argc, char **argv)
     loop();
 
     if (sent != received) {
-        eprintf("Not all sent messages were received.\n");
-        eprintf("Updated value %d time%s, but received %d of them.\n",
+        eprintf("Not all sent queries received responses.\n");
+        eprintf("Queried %d time%s, but received %d responses.\n",
                 sent, sent == 1 ? "" : "s", received);
         result = 1;
     }
diff --git a/test/testqueue.c b/test/testqueue.c
index 9fcdaac..b2c44ad 100644
--- a/test/testqueue.c
+++ b/test/testqueue.c
@@ -122,6 +122,11 @@ int create_connections()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
@@ -131,9 +136,9 @@ int create_connections()
     mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
     }
 
     mapper_monitor_free(mon);
@@ -143,7 +148,7 @@ int create_connections()
 
 void wait_ready()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
         usleep(500 * 1000);
diff --git a/test/testrate.c b/test/testrate.c
index d846fcb..cd3c10c 100644
--- a/test/testrate.c
+++ b/test/testrate.c
@@ -162,7 +162,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -179,14 +179,22 @@ int setup_connections()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+        if (i++ > 100)
+            return 1;
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
 
+    i = 0;
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
         if (i++ > 100)
             return 1;
     }
diff --git a/test/testrecv.c b/test/testrecv.c
index 885235a..d997b88 100644
--- a/test/testrecv.c
+++ b/test/testrecv.c
@@ -86,11 +86,6 @@ int test_recv()
         sent++;
         mdev_poll(md, 100);
         i++;
-
-        if (!verbose) {
-            printf("\r  Sent: %4i, Received: %4i   ", sent, received);
-            fflush(stdout);
-        }
     }
 
     if (sent != received) {
@@ -159,6 +154,6 @@ int main(int argc, char **argv)
     }
 
 done:
-    printf("Test %s.\n", result ? "FAILED" : "PASSED");
+    printf("               Test %s.\n", result ? "FAILED" : "PASSED");
     return result;
 }
diff --git a/test/testreverse.c b/test/testreverse.c
index dd27c00..63a9f8d 100644
--- a/test/testreverse.c
+++ b/test/testreverse.c
@@ -33,17 +33,29 @@ void insig_handler(mapper_signal sig, mapper_db_signal props,
                    int instance_id, void *value, int count,
                    mapper_timetag_t *timetag)
 {
+    int i;
     if (value) {
-        if (props->type == 'f')
-            eprintf("--> %s got %f\n", props->is_output ?
-                    "source" : "destination", (*(float*)value));
-        else if (props->type == 'i')
-            eprintf("--> %s got %i\n", props->is_output ?
-                    "source" : "destination", (*(int*)value));
+        if (props->type == 'f') {
+            eprintf("--> %s got ", props->is_output ?
+                    "source" : "destination");
+            for (i = 0; i < props->length * count; i++)
+                eprintf("%f ", ((float*)value)[i]);
+            eprintf("\n");
+        }
+        else if (props->type == 'i') {
+            eprintf("--> %s got ", props->is_output ?
+                    "source" : "destination");
+            for (i = 0; i < props->length * count; i++)
+                eprintf("%i ", ((int*)value)[i]);
+            eprintf("\n");
+        }
     }
     else {
-        eprintf("--> %s got NIL\n", props->is_output ?
+        eprintf("--> %s got ", props->is_output ?
                 "source" : "destination");
+        for (i = 0; i < props->length * count; i++)
+            eprintf("NIL ");
+        eprintf("\n");
     }
     received++;
 }
@@ -56,9 +68,9 @@ int setup_source()
         goto error;
     eprintf("source created.\n");
 
-    int mn=0, mx=10;
+    float mn[]={0.f,0.f}, mx[]={10.f,10.f};
 
-    sendsig = mdev_add_output(source, "/outsig", 1, 'i', 0, &mn, &mx);
+    sendsig = mdev_add_output(source, "/outsig", 2, 'f', 0, mn, mx);
     msig_set_callback(sendsig, insig_handler, 0);
 
     eprintf("Output signals registered.\n");
@@ -114,7 +126,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
@@ -125,12 +137,20 @@ void wait_local_devices()
 int setup_connections()
 {
     int i = 0;
+
     mapper_monitor mon = mapper_monitor_new(source->admin, 0);
 
     char src_name[1024], dest_name[1024];
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !destination->receivers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+        if (i++ > 100)
+            return 1;
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_db_connection_t props;
@@ -138,11 +158,11 @@ int setup_connections()
     mapper_monitor_connect(mon, src_name, dest_name, &props,
                            CONNECTION_MODE);
 
+    i = 0;
     // wait until connection has been established
-    while (!destination->receivers ||
-           !destination->receivers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !destination->receivers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
         if (i++ > 100)
             return 1;
     }
@@ -155,6 +175,9 @@ void loop()
 {
     eprintf("-------------------- GO ! --------------------\n");
     int i = 0;
+    float val[] = {0, 0};
+    msig_update(sendsig, val, 1, MAPPER_NOW);
+
     while ((!terminate || i < 50) && !done) {
         msig_update_float(recvsig, ((i % 10) * 1.0f));
         sent++;
diff --git a/test/testselect.c b/test/testselect.c
index 6921cab..32ab276 100644
--- a/test/testselect.c
+++ b/test/testselect.c
@@ -131,12 +131,19 @@ int setup_connection()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        if (count++ > 50)
+            goto error;
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
+    while (!done && !source->routers->num_connections) {
         if (count++ > 50)
             goto error;
         mdev_poll(source, 10);
@@ -154,7 +161,7 @@ int setup_connection()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
 
diff --git a/test/testsend.c b/test/testsend.c
index 16413ca..512c239 100644
--- a/test/testsend.c
+++ b/test/testsend.c
@@ -36,10 +36,11 @@ int test_controller()
     eprintf("Number of outputs: %d\n", mdev_num_outputs(md));
 
     const char *host = "localhost";
-    int port = 9000;
-    mapper_router rt = mapper_router_new(md, host, port, "DESTINATION");
+    int admin_port = 9000, data_port = 9001;
+    mapper_router rt = mapper_router_new(md, host, admin_port, data_port,
+                                         "DESTINATION");
     mdev_add_router(md, rt);
-    eprintf("Router to %s:%d added.\n", host, port);
+    eprintf("Router to %s:%d added.\n", host, data_port);
 
     mapper_router_add_connection(rt, sig, "/mapped1", 'f', 1);
     mapper_router_add_connection(rt, sig, "/mapped2", 'f', 1);
diff --git a/test/testsignals.c b/test/testsignals.c
index eeaa902..17ce899 100644
--- a/test/testsignals.c
+++ b/test/testsignals.c
@@ -9,6 +9,8 @@
 #include <signal.h>
 
 mapper_device mdev = 0;
+mapper_signal inputs[100];
+mapper_signal outputs[100];
 
 void sig_handler(mapper_signal sig, mapper_db_signal props,
                  int instance_id, void *value, int count,
@@ -40,25 +42,28 @@ int main(int argc, char ** argv)
     printf("Adding signals... ");
     fflush(stdout);
     for (i = 0; i < 100; i++) {
+        mdev_poll(mdev, 100);
         snprintf(signame, 32, "/s%i", i);
-        if (!mdev_add_input(mdev, signame, 1, 'f', 0, 0, 0, sig_handler, 0)) {
+        if (!(inputs[i] = mdev_add_input(mdev, signame, 1, 'f', 0, 0,
+                                         0, sig_handler, 0))) {
             result = 1;
             goto done;
         }
-        if (!mdev_add_output(mdev, signame, 1, 'f', 0, 0, 0)) {
+        if (!(outputs[i] = mdev_add_output(mdev, signame, 1, 'f', 0, 0, 0))) {
             result = 1;
             goto done;
         }
     }
-
-    printf("Waiting for 10 seconds... ");
-    fflush(stdout);
-    for (i = 100; i > 0; i--)
+    printf("Removing 200 signals...\n");
+    for (i = 0; i < 100; i++) {
+        mdev_remove_input(mdev, inputs[i]);
+        mdev_remove_output(mdev, outputs[i]);
         mdev_poll(mdev, 100);
-
-    mdev_free(mdev);
+    }
 
   done:
+    if (mdev)
+        mdev_free(mdev);
     printf("Test %s.\n", result ? "FAILED" : "PASSED");
     return result;
 }
diff --git a/test/testspeed.c b/test/testspeed.c
index 5c74986..803cca3 100644
--- a/test/testspeed.c
+++ b/test/testspeed.c
@@ -15,6 +15,7 @@
         fprintf(stdout, format, ##__VA_ARGS__); \
     else                                        \
         fprintf(stdout, ".");                   \
+    fflush(stdout);                             \
 } while(0)
 
 int verbose = 1;
@@ -134,7 +135,7 @@ void cleanup_destination()
 
 void wait_local_devices()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 25);
         mdev_poll(destination, 25);
     }
@@ -148,6 +149,11 @@ void connect_signals()
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_db_connection_t props;
@@ -157,9 +163,9 @@ void connect_signals()
                            CONNECTION_MODE | CONNECTION_EXPRESSION);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
     }
 
     mapper_monitor_free(mon);
diff --git a/test/testsync.c b/test/testsync.c
index e2e5b70..cae690f 100644
--- a/test/testsync.c
+++ b/test/testsync.c
@@ -92,7 +92,7 @@ void loop()
                     printf("\nSYSTEM TIME *****  |  OFFSETS *****\n");
                     for (i=0; i<5; i++) {
                         // Give each device clock a random starting offset
-                        devices[i]->admin->clock.offset = (rand() % 100) - 50;
+//                        devices[i]->admin->clock.offset = (rand() % 100) - 50;
                     }
                     ready = 1;
                 }
diff --git a/test/testvector.c b/test/testvector.c
index 7d757df..386a7ab 100644
--- a/test/testvector.c
+++ b/test/testvector.c
@@ -99,20 +99,32 @@ void cleanup_destination()
 
 int setup_connections()
 {
+    int i = 0;
     mapper_monitor mon = mapper_monitor_new(source->admin, 0);
 
     char src_name[1024], dest_name[1024];
     mapper_monitor_link(mon, mdev_name(source),
                         mdev_name(destination), 0, 0);
 
+    // wait until link has been established
+    while (!done && !source->routers) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+        if (i++ > 100)
+            return 1;
+    }
+
     msig_full_name(sendsig, src_name, 1024);
     msig_full_name(recvsig, dest_name, 1024);
     mapper_monitor_connect(mon, src_name, dest_name, 0, 0);
 
     // wait until connection has been established
-    while (!source->routers || !source->routers->n_connections) {
-        mdev_poll(source, 1);
-        mdev_poll(destination, 1);
+    i = 0;
+    while (!done && !source->routers->num_connections) {
+        mdev_poll(source, 10);
+        mdev_poll(destination, 10);
+        if (i++ > 100)
+            return 1;
     }
 
     mapper_monitor_free(mon);
@@ -122,7 +134,7 @@ int setup_connections()
 
 void wait_ready()
 {
-    while (!(mdev_ready(source) && mdev_ready(destination))) {
+    while (!done && !(mdev_ready(source) && mdev_ready(destination))) {
         mdev_poll(source, 0);
         mdev_poll(destination, 0);
         usleep(500 * 1000);

-- 
libmapper packaging



More information about the pkg-multimedia-commits mailing list