# How to Implement Cross-Device Parameter Binding with Value Conversion in Nallely

> Learn to implement cross-device parameter binding with value conversion in Nallely MIDI. Use Scaler and bind() for automatic mapping and conversion.

- Repository: [dr-schlange/nallely-midi](https://github.com/dr-schlange/nallely-midi)
- Tags: how-to-guide
- Published: 2026-02-28

---

**To implement cross-device parameter binding with value conversion in Nallely, retrieve `ParameterInstance` objects from your `VirtualDevice` instances, optionally apply a `Scaler` via the `.scale()` method to handle range conversion, and invoke `.bind()` on the source to create a `Link` that automatically handles linear/logarithmic mapping and conversion policies.**

Nallely treats every controllable knob, slider, pad, or CV output as a **`VirtualParameter`** living on a **`VirtualDevice`**. When you need to synchronize parameters across different hardware or virtual instruments in the [dr-schlange/nallely-midi](https://github.com/dr-schlange/nallely-midi) ecosystem, the library provides a declarative binding system that handles value conversion automatically through `Scaler` objects and conversion policies.

## Understanding the Core Architecture

Before implementing bindings, you must understand four key classes that orchestrate the data flow between devices.

### VirtualParameter and Conversion Policies

In [`nallely/core/virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/virtual_device.py), the `VirtualParameter` class defines the schema for any controllable value. Each parameter can store a **`conversion_policy`** that dictates how incoming values should be coerced before reaching the destination hardware.

The supported policies are:

- **`"round"`** – Casts the value to the nearest integer
- **`">0"`** – Clamps negative values to 0 (useful for gate-type CV)
- **`"!=0"`** – Converts any non-zero value to 1 (binary on/off)

These policies are stored on the `VirtualParameter` and automatically consulted by the scaling engine during value transmission.

### ParameterInstance and the Bind Method

When you access a parameter through a device attribute (e.g., `device.speed_cv`), Nallely returns a **`ParameterInstance`** defined in [`nallely/core/parameter_instances.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/parameter_instances.py). This wrapper exposes the `.bind()` method, which delegates to `Link.create(self, target)` to establish the connection.

### Link and Scaler Chains

The `Link` class in [`nallely/core/links.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/links.py) wires the source to the destination. If the source and target ranges differ, Nallely automatically inserts a **`Scaler`** into the link chain. The `Scaler` class in [`nallely/core/scaler.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/scaler.py) implements:

- **Linear mapping** (`method="lin"`) – Direct proportion mapping between intervals
- **Logarithmic mapping** (`method="log"`) – Exponential curve mapping for frequency-based parameters
- **Policy enforcement** – Applies the destination's `conversion_policy` inside methods like `convert_lin()`

## Step-by-Step Implementation Guide

Follow these steps to bind parameters across devices with automatic value conversion.

### 1. Create or Retrieve Device Instances

Instantiate your virtual or physical devices. Every CV output or input is automatically wrapped as a `ParameterInstance`.

```python
from nallely import LFO, Synth

lfo = LFO()      # Virtual LFO with speed_cv (0-127)

synth = Synth()  # Virtual synth with filter_cv (0-127)

```

### 2. Select Source and Destination Parameters

Access the parameters through the device attributes. These return `ParameterInstance` objects capable of binding.

```python
src_param = lfo.speed_cv      # ParameterInstance

dst_param = synth.filter_cv   # ParameterInstance

```

### 3. Configure Conversion Policies (Optional)

If the destination requires specific value coercion, set the policy on the underlying `VirtualParameter` before binding.

```python

# Ensure the synth receives only integer values

dst_param.parameter.conversion_policy = "round"

```

### 4. Create the Binding with Scaling

Call `.bind()` on the source parameter. If the ranges differ, pass a scaled view of the destination using `.scale()`.

```python

# Bind with automatic linear scaling (0-127 → 0-127)

src_param.bind(dst_param.scale(to_min=0, to_max=127, method="lin", as_int=True))

```

The `.scale()` method returns a `Scaler` object that `Link.create()` inserts into the processing chain. When the LFO speed changes, the value flows through `Scaler.convert_lin()`, applies the rounding policy, and arrives at the synth's filter.

## Advanced Value Conversion Methods

When binding parameters with different value ranges or scaling curves, explicitly configure the `Scaler` behavior.

### Linear vs. Logarithmic Scaling

Use `method="log"` for frequency-based parameters to achieve perceptually smooth transitions across wide ranges.

```python

# Map LFO speed (0-127) to synth cutoff (20-20000 Hz) logarithmically

lfo.speed_cv.bind(
    synth.cutoff_cv.scale(to_min=20, to_max=20000, method="log", as_int=False)
)

```

The `convert_lin()` and equivalent logarithmic methods in [`nallely/core/scaler.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/scaler.py) handle the mathematical transformation while respecting the `as_int` flag and any active conversion policies.

### Integer vs. Float Output

Set `as_int=True` when the destination expects MIDI CC values or discrete steps. Set `as_int=False` for continuous CV control.

```python

# Integer output for MIDI compatibility

src_param.bind(dst_param.scale(to_min=0, to_max=127, method="lin", as_int=True))

```

## Remote Binding via the Trevor API

For UI-driven or script-based binding over WebSocket, use the Trevor API exposed in [`nallely/trevor/trevor_api.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/trevor_api.py). This allows cross-device binding without direct Python object access.

```python
from nallely.trevor import TrevorClient

trevor = TrevorClient("ws://localhost:3000")
trevor.bind(
    src_device="my_lfo", src_parameter="speed_cv",
    dst_device="my_synth", dst_parameter="filter_cv",
    conversion="lin",    # Optional: "lin" or "log"

    as_int=True        # Request integer output

)

```

The `bind()` function in the Trevor API mirrors the local implementation, automatically creating the `Link` and inserting a `Scaler` when conversion parameters are provided.

## Summary

- **Architecture**: Nallely uses `VirtualParameter` definitions, `ParameterInstance` wrappers, `Link` connections, and `Scaler` conversions to route values between devices.
- **File Locations**: Core logic resides in [`nallely/core/virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/virtual_device.py) (policies), [`nallely/core/parameter_instances.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/parameter_instances.py) (binding entry point), [`nallely/core/scaler.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/scaler.py) (value conversion), and [`nallely/core/links.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/links.py) (connection management).
- **Implementation Pattern**: Retrieve instances via device attributes, optionally configure `conversion_policy` on the destination, then call `source.bind(destination.scale(...))` to establish the connection with automatic value mapping.
- **Scaling Options**: Choose `method="lin"` for linear mapping, `method="log"` for exponential curves, and `as_int` to control numeric precision.
- **Remote Access**: The Trevor WebSocket API in [`nallely/trevor/trevor_api.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/trevor_api.py) exposes identical binding capabilities for external scripts and UI components.

## Frequently Asked Questions

### What is the difference between a VirtualParameter and a ParameterInstance?

A **`VirtualParameter`** defines the specification, range, and conversion policy for a control (defined in [`nallely/core/virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/virtual_device.py)). A **`ParameterInstance`** is a runtime wrapper that represents that parameter on a specific device instance and provides the `.bind()` method (implemented in [`nallely/core/parameter_instances.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/parameter_instances.py)). You interact with instances to create links; you configure the underlying `VirtualParameter` to set conversion policies.

### How does Nallely handle mismatched value ranges between devices?

When you pass a scaled view of the destination parameter via `.scale(to_min, to_max, method)`, Nallely automatically inserts a **`Scaler`** object into the `Link` chain. The `Scaler` intercepts values from the source, applies linear or logarithmic transformation via `convert_lin()` or equivalent methods, and delivers the converted value to the destination. This happens transparently whenever the source value changes.

### Can I apply multiple conversion policies to a single parameter?

No, each `VirtualParameter` supports a single **`conversion_policy`** string (either `"round"`, `">0"`, or `"!=0"`). However, you can combine this policy with custom scaling logic by pre-processing values in event callbacks or by chaining multiple virtual devices. The policy is applied automatically by the `Scaler` during the final conversion step before the value reaches the hardware.

### Does the Trevor API support the same conversion features as direct Python binding?

Yes. The `trevor.bind()` function in [`nallely/trevor/trevor_api.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/trevor_api.py) accepts `conversion` (specifying `"lin"` or `"log"`) and `as_int` parameters, mirroring the functionality of the local `ParameterInstance.bind()` and `.scale()` methods. The WebSocket server creates the equivalent `Link` and `Scaler` objects on the backend, ensuring consistent behavior whether you bind programmatically or through the remote UI.