memlnaut-nisps/vcv/NISPS-FORMAT.md
w1n5t0n ba0fcab2a2 feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay

Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED

Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
  presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 01:48:44 +02:00

8.3 KiB

.nisps File Format Specification

Version: 1

Overview

The .nisps format is a JSON file that captures the complete state of a NISPS interactive ML engine: network weights, training examples, I/O configuration, and MLP architecture metadata. It enables preset sharing between the VCV Rack module and the web playground.

Top-Level Structure

{
  "version": 1,
  "noiseLevel": 0.1,
  "slewMs": 10.0,
  "outputRangeUnipolar": [true, false, ...],
  "inputRangeUnipolar": [true, true, ...],
  "weights": [[[...], ...], ...],
  "examples": {
    "features": [[...], ...],
    "labels": [[...], ...]
  },
  "mlpConfig": {
    "layers": [3, 16, 24, 16, 12],
    "activations": ["relu", "relu", "relu", "sigmoid"]
  },
  "params": [0.5, 0.8, ...]
}

Field Reference

version (integer, required)

Format version number. Currently 1. Loaders must reject files where version < 1. Future versions will increment this value; loaders should accept any version they understand and reject higher versions gracefully.

noiseLevel (float, optional)

RL exploration noise amplitude. Range: 0.0-1.0. Default: 0.1. Controls how much random perturbation is applied to weights during reinforcement learning exploration.

slewMs (float, optional)

Output slew rate in milliseconds. Default: 10.0. Smooths transitions between output values to prevent clicks/artifacts. In VCV Rack this is the time constant for exponential smoothing on output voltages.

outputRangeUnipolar (boolean[], optional)

Per-output voltage range flag. Length must equal the number of MLP outputs. When true, the output maps to 0-10V (unipolar); when false, it maps to +/-5V (bipolar). Default: all false.

This field is VCV Rack-specific. The web playground ignores it but should preserve it on round-trip.

inputRangeUnipolar (boolean[], optional)

Per-input voltage range flag. Length must equal the maximum number of inputs (8 in VCV Rack). When true, the input expects 0-10V; when false, +/-5V. Default: all false.

This field is VCV Rack-specific. The web playground ignores it but should preserve it on round-trip.

weights (float[][][], required for state restore)

MLP weights as a 3D array: weights[layer][node][weight].

Serialization order:

  • Outer dimension: layers, from first hidden layer to output layer. Length = number of layers - 1 (i.e., number of weight matrices).
  • Middle dimension: nodes within that layer. Length = mlpConfig.layers[i+1] (the number of nodes in the destination layer).
  • Inner dimension: connection weights from source nodes. Length = mlpConfig.layers[i] (the number of nodes in the source layer, including bias for the first layer).

Bias handling: In the VCV Rack C++ implementation, bias is stored separately from connection weights and is not included in this array. The bias values are not serialized. On load, biases remain at their current values (typically 0).

In the web playground, bias is also stored separately per node. The webapp's export/import methods handle the structural difference by serializing weights without bias (matching the VCV format) and preserving bias in a separate optional field.

Example for a [3, 4, 2] network (3 inputs, 4 hidden, 2 outputs):

"weights": [
  [                          // layer 0: input -> hidden
    [0.1, -0.3, 0.5],       // hidden node 0: 3 weights from 3 inputs
    [0.2, 0.4, -0.1],       // hidden node 1
    [-0.6, 0.3, 0.2],       // hidden node 2
    [0.1, -0.5, 0.7]        // hidden node 3
  ],
  [                          // layer 1: hidden -> output
    [0.3, -0.2, 0.4, 0.1],  // output node 0: 4 weights from 4 hidden nodes
    [-0.1, 0.5, -0.3, 0.2]  // output node 1
  ]
]

examples (object, optional)

Training examples as parallel arrays.

examples.features (float[][], required if examples present)

Input feature vectors. Each inner array has length equal to mlpConfig.layers[0] minus the bias term. In the VCV module, features are stored without the bias term appended.

examples.labels (float[][], required if examples present)

Output label vectors. Each inner array has length equal to mlpConfig.layers[last]. Must have the same outer length as features.

Example:

"examples": {
  "features": [
    [0.3, 0.7],
    [0.8, 0.2]
  ],
  "labels": [
    [0.1, 0.9, 0.5, 0.3, 0.7, 0.2, 0.8, 0.4, 0.6, 0.1, 0.5, 0.3],
    [0.9, 0.1, 0.4, 0.7, 0.3, 0.8, 0.2, 0.6, 0.4, 0.9, 0.5, 0.7]
  ]
}

mlpConfig (object, required)

Architecture metadata for validation on load.

mlpConfig.layers (integer[], required)

Layer sizes including input (with bias) and output. For the default VCV configuration with 2 inputs and 12 outputs: [3, 16, 24, 16, 12]. The first element includes the +1 bias node.

mlpConfig.activations (string[], optional)

Activation function names per weight layer. Length = layers.length - 1. Valid values: "relu", "sigmoid", "tanh", "linear". Default: all hidden layers use "relu", output layer uses "sigmoid".

The VCV Rack module currently does not serialize this field (the architecture is fixed). The web playground includes it for forward compatibility.

params (float[], optional)

VCV Rack module parameter values (knob positions, attenuators, etc.). Array indexed by the module's parameter enum. Present only in files saved from the VCV Rack "Save .nisps preset" menu.

The web playground may use this field to store synth parameter values for preset portability. Consumers that don't understand the parameter layout should ignore this field.

Validation Rules

Loaders should check the following on import:

  1. Version gate: version must be present and >= 1. Reject unknown future versions.
  2. Weight dimensions: If weights and mlpConfig.layers are both present, verify:
    • weights.length === mlpConfig.layers.length - 1
    • weights[i].length === mlpConfig.layers[i + 1]
    • weights[i][j].length === mlpConfig.layers[i]
  3. Example dimensions: If examples is present:
    • features.length === labels.length
    • Each feature vector length should equal mlpConfig.layers[0] (or mlpConfig.layers[0] - 1 if bias is excluded)
    • Each label vector length should equal mlpConfig.layers[last]
  4. Architecture compatibility: If the loader's MLP has a different architecture than mlpConfig.layers, the file cannot be loaded directly. The loader should reject or warn.
  5. Numeric validity: All weight and example values must be finite (not NaN or Infinity).

Compatibility Notes

VCV Rack -> Web Playground

  • The VCV module uses a [3, 16, 24, 16, 12] architecture (2 inputs + bias, 12 outputs).
  • The web playground uses a [3, 32, 48, 64, 126] architecture (2 inputs + bias, 126 outputs).
  • Direct weight transfer between these architectures is not possible. The mlpConfig.layers field enables loaders to detect this mismatch and report it.
  • Training examples (features/labels) are also architecture-dependent due to different output counts.

Bias Values

The VCV C++ serialization does not include node bias values in the weights array. Bias is stored separately in Node::m_bias but is not written to JSON. This means bias values are reset to their pre-load state when restoring from a .nisps file. In practice this has minimal impact because:

  • The MLP uses leaky ReLU (hidden) and sigmoid (output) activations
  • Training quickly adjusts bias values
  • Initial bias is typically 0

Future Extensions

New fields may be added to the top level without incrementing the version number, as long as they are optional and backward-compatible. The version number increments only for breaking changes to existing field semantics.

Complete Example

A minimal but complete .nisps file for a [3, 4, 2] network (2 inputs, 4 hidden nodes, 2 outputs):

{
  "version": 1,
  "noiseLevel": 0.15,
  "slewMs": 10.0,
  "outputRangeUnipolar": [true, false],
  "inputRangeUnipolar": [false, false],
  "weights": [
    [
      [0.123, -0.456, 0.789],
      [-0.321, 0.654, -0.987],
      [0.111, -0.222, 0.333],
      [-0.444, 0.555, -0.666]
    ],
    [
      [0.12, -0.34, 0.56, -0.78],
      [0.91, -0.23, 0.45, -0.67]
    ]
  ],
  "examples": {
    "features": [
      [0.3, 0.7],
      [0.8, 0.2]
    ],
    "labels": [
      [0.9, 0.1],
      [0.2, 0.8]
    ]
  },
  "mlpConfig": {
    "layers": [3, 4, 2],
    "activations": ["relu", "sigmoid"]
  }
}