Skip to content

Repository files navigation

js2max

Write Max for Live devices in JavaScript. Compile to .amxd and drag into Ableton Live.

js2max is a Node.js compiler that takes a JavaScript file written for the Max 9 v8 engine and produces a ready-to-use .amxd device. No visual patching required.

Why?

The Max/MSP visual editor is great for experimentation but painful for serious development:

  • No version control (binary/JSON diffs are unreadable)
  • No code review, no linting, no refactoring tools
  • AI coding assistants can't help you patch
  • Copy-paste logic between devices is manual and error-prone

Max 9 introduced the v8 object — a modern V8 JavaScript engine with full ES6+ support and access to the Live Object Model. Your logic is pure JavaScript; you just need a minimal patch to wire it up.

js2max generates that patch for you.

Quick Start

npx js2max compile effect.js                  # → effect.amxd (drag into Ableton Live)
npx js2max compile effect.js -o effect.maxpat  # → JSON for Max 9

Installation

npm install -g js2max

Or use directly with npx:

npx js2max compile <input.js> [options]

Usage

Write your JavaScript

// @device midi-effect

inlets = 2;
outlets = 1;

function msg_int(value) {
  if (inlet === 0) {
    // Control message
    post("Received:", value, "\n");
    return;
  }
  // MIDI byte — pass through doubled
  outlet(0, value);
}

function bang() {
  outlet(0, "hello", "world");
}

Compile it

# MIDI effect (default) → .amxd
js2max compile effect.js

# Audio effect
js2max compile delay.js --type audio-effect

# Instrument
js2max compile synth.js --type instrument

# Output as .maxpat instead
js2max compile effect.js -o effect.maxpat

CLI Options

Option Default Description
-o, --output <path> <input>.amxd Output file path (.amxd or .maxpat)
-t, --type <type> midi-effect Device type: midi-effect, audio-effect, instrument
--no-embed (embeds by default) Reference external .js file instead of embedding
-w, --device-width <px> 400 Max for Live device strip width

Output Formats

  • .amxd (default) — Binary Max for Live device. Opens directly in Ableton Live (any version with Max for Live support).
  • .maxpat — JSON Max patch. Opens in Max 9. Use -o file.maxpat to select this format.

The .amxd binary format was reverse-engineered from Ableton's own maxdevtools and validated against working devices. It uses a chunk-based container (ampfmetaptch with mx@c header and dlst directory listing).

Decorator Comments

Use comments at the top of your file to configure compilation:

// @device midi-effect          — sets device type (overridden by --type flag)
// @inlet 0 "MIDI input"       — help text for inlet 0
// @inlet 1 "Control"          — help text for inlet 1
// @outlet 0 "Processed MIDI"  — help text for outlet 0

@ui — Device Strip UI Elements

Add interactive controls to your device's presentation view with @ui decorators. The compiler generates Max UI objects, wires them to your v8 inlets/outlets, and auto-layouts them in the device strip.

// @ui live.text "Fire" trigger inlet=0        — button (sends bang on click)
// @ui live.dial "Delay" inlet=1 min=0 max=1000 — rotary dial
// @ui live.slider "Volume" inlet=2 min=0 max=127 — vertical slider
// @ui live.toggle "Active" outlet=0           — LED toggle (driven by code)

Format: // @ui <maxclass> "<label>" [trigger] inlet=N|outlet=N [min=X] [max=Y]

Parameter Description
inlet=N Wires UI output to v8 inlet N (user controls the code)
outlet=N Wires v8 outlet N to UI input (code controls the display)
trigger For live.text — button mode (bang on click) instead of toggle
min/max Value range for dials and sliders

Example — MIDI echo with UI:

// @device midi-effect
// @ui live.dial "Delay" inlet=0 min=0 max=1000
// @ui live.dial "Feedback" inlet=2 min=0 max=100
// @ui live.toggle "Activity" outlet=1

inlets = 3;
outlets = 2;

function msg_int(value) {
  if (inlet === 0) {
    delayTime = value;
    return;
  }
  if (inlet === 2) {
    feedback = value / 100;
    return;
  }
  outlet(0, value);
}

Elements are arranged in a row below the title and wrap automatically when they exceed the device width.

How It Works

  1. Parses your .js file to extract inlets, outlets, handler functions, and decorator comments
  2. Selects a template based on device type (MIDI effect, audio effect, or instrument)
  3. Generates the patch JSON with the v8 object wired to the appropriate Max for Live infrastructure (midiin/midiout, plugin~/plugout~, live.thisdevice)
  4. Embeds your JavaScript source directly in the patch (single-file distribution)
  5. Wraps in the .amxd binary container (chunk-based: ampf + meta + ptch with mx@c header and dlst directory)

Examples

See the examples/ directory:

  • hello-world.js — Simplest possible device with a "Say Hello" button
  • midi-echo.js — MIDI delay/echo with Delay/Feedback dials and Activity toggle
  • clip-launcher.js — Live Object Model with Track/Slot dials and Fire/Stop buttons

Max 9 v8 API Quick Reference

The v8 object gives you access to these globals:

// Inlets and outlets (must be in global scope)
inlets = 2;
outlets = 3;
inlet; // read-only: which inlet triggered the current function

// Output
outlet(n, ...args); // send message out outlet n
outlet_array(n, arr); // send JS array (v8 only)
outlet_dictionary(n, dict); // send dictionary (v8 only)

// Message handlers (define as global functions)
function bang() {}
function msg_int(n) {}
function msg_float(f) {}
function list(...args) {}
function anything(msg, ...args) {} // catch-all

// Live Object Model
var api = new LiveAPI(callback, "live_set tracks 0");
api.get("name");
api.set("mute", 1);
api.call("fire");

// Utilities
post("debug message\n"); // print to Max console

Full reference: Max JavaScript User Guide | v8 Reference | Live API

Limitations

  • No audio DSP: JavaScript runs in Max's low-priority thread. Use v8 for MIDI processing, generative tools, control logic, and Live Object Model access — not for real-time audio processing.

Development

git clone https://github.com/ktamas77/js2max.git
cd js2max
npm install
npm run build
npm test

Code Quality

Pre-commit hooks (via husky) automatically run on every commit:

  • TypeScript typecheck (tsc --noEmit)
  • Prettier formatting
  • ESLint linting
npm run typecheck    # type check
npm run lint         # eslint
npm run format       # prettier --write

Changelog

See CHANGELOG.md for release history.

License

MIT

About

Compile JavaScript files into Max for Live (.amxd) devices

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages