This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Knobbler4 is a Max for Live device that transforms a tablet/phone into an auto-labeling, multitouch parameter control surface for Ableton Live. It communicates with mobile apps via OSC (Open Sound Control) protocol and uses the Ableton Live API to interact with tracks, devices, and parameters.
Code editing happens in a VSCode Devcontainer environment. The TypeScript source files in src/ are edited within the container, which provides a Node.js 20 development environment with Prettier and ESLint extensions pre-configured.
Opening the devcontainer:
# From the repository root
dc # Shell alias for 'devcontainer open .'
# Or manually:
devcontainer open .Container setup:
- Based on
node:20Docker image - Mounts the repository to
/appinside the container - Automatically runs
npm run dev(TypeScript watch mode) on startup - Uses a Docker volume for
node_modulesto improve performance - Pre-configured VSCode extensions: Prettier-ESLint, Prettier, Makefile Tools
Workflow: Edit TypeScript files in src/ within the devcontainer → auto-compile via watch mode → compiled .js files appear in Project/ → test changes in Ableton Live with the device reloaded.
# Install dependencies
yarn install --frozen-lockfile
# Build TypeScript to JavaScript (compiles src/*.ts → Project/*.js)
yarn build
# Watch mode for development (auto-rebuild on changes)
yarn dev# Lint TypeScript files
npx eslint src/**/*.ts
# Format code with Prettier
npx prettier --write src/**/*.tsTypeScript source files in src/ compile to JavaScript in Project/ directory. The compiled .js files are loaded by [js] objects in Max patches. TypeScript provides type safety and IDE support while Max's JavaScript engine executes the compiled CommonJS output.
Key compilation setting: tsconfig.json targets ES5 with CommonJS modules, output to Project/ directory.
router.ts - Central OSC message dispatcher
- Receives all incoming OSC messages from the tablet
- Uses prefix-based routing with ~50 routing rules in the ROUTER array
- Distributes messages to 11 outlets for different functional areas (knobbler, bluhand, mixer, page, etc.)
- Handler pattern:
bareMsg,bareVal,stdVal,stdSlot,stdSlotVal
knobblerCore.ts - Core parameter mapping engine (1300+ lines)
- Manages 32 parameter "slots" that users map to Ableton device parameters
- Maintains parallel arrays tracking: parameter objects, names, device info, track info, colors, values
- Implements LiveAPI observers/watchers for real-time parameter updates
- Handles debouncing to prevent feedback loops between OSC and Ableton
- Scales values between OSC range (0-1) and Ableton parameter ranges
- Stores mapping configuration that persists with Live sets
knobbler4.ts - Main entry point wrapper
- Sets up Max [js] object with 1 inlet and 2 outlets
- Delegates all operations to
knobblerCore - Minimal glue layer between Max and core functionality
k4-tracksDevices.ts - Track/device navigation tree builder
- Builds hierarchical structures for tracks, returns, and master
- Generates display data for tablet's navigation browser
- Watches for changes to track counts, names, colors, and folding states
k4-bluhandBanks.ts - Device parameter bank management
- Manages parameter banks for controlling selected devices
- Handles Ableton instruments with multiple parameter pages
- Imports device bank definitions from
deviceParams.ts
k4-mixerSends.ts - Mixer control interface
- Manages volume, pan, crossfader, mute, solo, sends
- Implements pause/unpause logic to prevent feedback during rapid changes
k4-deviceParamMaps.ts - Device parameter lookup utility
- Simple module mapping device names to their parameter banks
k4-currentParam.ts - Selected parameter display
- Follows Live's
selected_parameterview property (mode=1 observer) - Pushes parameter name, device name, track name, track color, value, and min/max strings to the app
- Supports lock/unlock to freeze the display on a specific parameter
- Observes value changes and track color changes in real time
k4-multiMixer.ts - Multi-track mixer
- Full-screen, horizontally scrollable mixer with per-strip observers
- Windowed approach: observers only active for visible strips
- Per-strip observers for volume, pan, mute, solo, arm, color, sends, meters, and volume automation state
- Batched meter output via
/mixer/metersJSON array
k4-oscBatch.ts - OSC message batching
- Batches outbound OSC messages into single
/batchJSON payloads - Flushes every 10ms or when buffer exceeds 1KB
- Only active when the client advertises
batchcapability - Bypasses batching for chunked data and meter messages
k4-discovery.ts - Network device discovery
- Discovers and filters available Knobbler apps on the network
deviceParams.ts (3000+ lines) - Comprehensive device parameter bank definitions sourced from Ableton's MIDI control scripts. Covers stock Live devices and Max for Live devices.
utils.ts - Helper functions including logging factory, path validation, color conversion, string truncation, debounced task management.
consts.ts - Constants and type definitions including MAX_SLOTS = 32, track types, outlet indices, default colors.
toggleInput.ts - Track input routing enable/disable functionality.
deprecatedMethods.ts - Legacy track/device navigation functions kept for backward compatibility.
Tablet (OSC) → [udpreceive] → router.js (dispatch by prefix)
→ Outlets (0-10) → Feature modules (knobbler4, k4-bluhand, k4-mixer, etc.)
→ LiveAPI ↔ Ableton Live (real-time sync)
→ [udpsend] → Tablet (OSC responses)
- Outlet-Based Distribution: Router immediately distributes messages to appropriate outlets allowing parallel processing in different Max subpatches
- LiveAPI Observer Pattern: Extensive use of Ableton LiveAPI observers to watch for changes and push updates to controller in real-time
- 32-Slot Parallel Arrays: Core parameter mapping uses indexed parallel arrays for efficient slot management
- Debouncing Strategy: Multiple debouncing approaches prevent infinite loops when values change from OSC input vs. direct manipulation in Live
- Module Export Pattern: TypeScript files use
const module = {}; export = {}pattern to ensure valid CommonJS for Max's JavaScript engine
The device communicates via OSC messages. See docs/OSC-API.md for complete protocol documentation including:
- Parameter mapping (
/valN,/unmapN,/defaultN) - Device control (Bluhand banks:
/bvalN,/bbankNext,/bbankPrev) - Navigation (
/gotoTrack,/gotoDevice,/gotoChain) - Mixer controls (
/mixer/vol,/mixer/pan,/mixer/sendN) - Toolbar/transport (
/tempo,/metronome,/isPlaying)
Main Device: Project/Knobbler4.amxd is the Max for Live audio device (added to Master track)
Key Max Objects:
[js router]- Loadsrouter.jsfor OSC message routing[js knobbler4]- Loadsknobbler4.jsfor core parameter functionality[js k4-bluhandBanks],[js k4-mixerSends], etc. - Load feature modules[udpreceive 9000]/[udpsend]- Network communication[poly~ finger]- Multi-touch gesture detection- bpatchers - Repeating UI elements for parameter slots
The frozen/ directory contains historical releases (.amxd device files and .tosc TouchOSC templates). Current development happens in src/ and Project/. After building, the Project/Knobbler4.amxd file is the distributable device.
Version numbers are manually updated in the changelog and device itself. Releases include the compiled .amxd file published to GitHub releases.
- Remember to commit compiled JavaScript files from
Project/directory - they are build artifacts currently tracked in git - Test in Ableton Live: Changes require testing in an actual Live set with the device loaded
- OSC Testing: Use OSC debugging tools or the actual tablet app to test message handling
- Debouncing: When adding new parameter observers, always implement debouncing to prevent feedback loops
- Max Console: Check Max's console window for JavaScript errors and log output during development
- LiveAPI
.idreturns a string: Always use+obj.id === 0(unary plus), neverobj.id === 0(strict equality"0" === 0isfalse). Same for!== 0checks. When passing.idto another LiveAPI's.idsetter, useparseInt(). - Max
require()does NOT cache modules: Eachrequire('./utils')call in a different file creates a separate module instance with its own state. Ifknobbler4.jsandknobblerCore.jsboth requireutils.js, they get independent copies. To share state (likesetDictPrefix), explicitly forward calls between modules. new Dict(name)resets parameter-enabled dicts: Creating a newDictreference to a parameter-enabled[dict]can reset its contents. CacheDictreferences as singletons instead of creating new ones on each access.- Dict persistence: One shared
settingsDictwithparameter_enablestores all settings. Per-instance keys (xyPairs, metersEnabled) are prefixed with the device's---value viasaveInstanceSetting/loadInstanceSetting. Shared keys (clientVersion, clientCapabilities, visibleTracks) usesaveSetting/loadSettingwithout prefix. The---prefix is sent to [js] objects viasetDictPrefixfrom thelive.thisdeviceinit chain.