This document describes the JavaScript API available for developing Move Anything modules.
The Move has a 128x64 pixel 1-bit (black/white) display.
clear_screen() // Clear display to black
print(x, y, text, color) // Draw text at position (color: 0=black, 1=white)
set_pixel(x, y, value) // Set pixel at position (value: 0=black, 1=white)
draw_rect(x, y, w, h, value) // Draw rectangle outline
fill_rect(x, y, w, h, value) // Draw filled rectangle
text_width(text) // Get width of text string in pixelsAn object-oriented display API is also available:
display.clear() // Clear display to black
display.drawText(x, y, text, color) // Draw text at position
display.fillRect(x, y, w, h, value) // Draw filled rectangle
display.drawRect(x, y, w, h, value) // Draw rectangle outline
display.drawLine(x1, y1, x2, y2, value) // Draw line between two points
display.flush() // Force immediate display update// Send to external USB-A port
move_midi_external_send([cable, status, data1, data2])
// Send to internal Move hardware (LEDs, etc.)
move_midi_internal_send([type, status, note, value])Cable numbers for external send: 0x00-0x0F (shifted left 4 bits in real MIDI)
Type codes for internal send:
0x09- Note messages (for LED control)0x0b- CC messages (for LED control)
// Called when MIDI arrives from external USB-A devices
globalThis.onMidiMessageExternal = function(data) {
// data = [status, data1, data2] or [status, data1, data2, data3...]
}
// Called when MIDI arrives from Move hardware (pads, knobs, buttons)
globalThis.onMidiMessageInternal = function(data) {
// data = [status, data1, data2]
}// Called once when module loads
globalThis.init = function() {
// Initialize state, set up LEDs, etc.
}
// Called every frame (~60fps)
globalThis.tick = function() {
// Update display, handle animations
}For list-based screens (title/list/footer), import the shared menu layout helpers:
import {
drawMenuHeader,
drawMenuList,
drawMenuFooter,
menuLayoutDefaults
} from '../../shared/menu_layout.mjs';import { decodeDelta, decodeAcceleratedDelta } from '../../shared/input_filter.mjs';
// Decode jog wheel CC value to simple ±1 delta
const delta = decodeDelta(value); // -1, 0, or 1
// Decode with velocity-sensitive acceleration (for value editing)
const accelDelta = decodeAcceleratedDelta(value, encoderId); // -10 to +10Acceleration behavior:
- Slow turns (>150ms): step = 1
- Fast turns (<25ms): step = 10
- In between: interpolated
import { setLED, setButtonLED, clearAllLEDs } from '../../shared/input_filter.mjs';
setLED(note, color); // Set pad/step LED (uses note messages)
setButtonLED(cc, color); // Set button LED (uses CC messages)
clearAllLEDs(); // Clear all LEDsLED values are cached to avoid redundant MIDI sends.
host_list_modules() // Returns [{id, name, version}, ...]
host_load_module(id_or_index) // Load a module by ID or index
host_load_ui_module(path) // Load a UI module file (for chain UI shims)
host_unload_module() // Unload current module
host_return_to_menu() // Unload module and return to host menu UI
host_module_set_param(k, v) // Set DSP parameter
host_module_get_param(k) // Get DSP parameter
host_module_get_error() // Get error message from last module operation
host_module_send_midi(msg, source) // Send MIDI into current DSP module
host_is_module_loaded() // Returns bool
host_get_current_module() // Returns {id, name, version, ui_script} or null
host_rescan_modules() // Rescan modules directory, returns count
// Host volume control
host_get_volume() // Returns volume (0-100)
host_set_volume(vol) // Set host output volume (0-100)
// Host settings
host_get_setting(key) // Get setting value (velocity_curve, aftertouch_enabled, aftertouch_deadzone)
host_set_setting(key, val) // Set setting value
host_save_settings() // Save settings to disk
host_reload_settings() // Reload settings from disk
// Display control
host_flush_display() // Force immediate display update
host_set_refresh_rate(hz) // Set display refresh rate (default ~11Hz)
host_get_refresh_rate() // Get current refresh rate
// File system utilities (used by Module Store)
host_file_exists(path) // Returns bool - check if file/directory exists
host_read_file(path) // Returns file contents as string, or null on error
host_write_file(path, content) // Write string content to file, returns bool
host_http_download(url, dest) // Download URL to dest path, returns bool
host_extract_tar(tarball, dir) // Extract .tar.gz to directory, returns bool
host_extract_tar_strip(tarball, dir, strip) // Extract with --strip-components
host_ensure_dir(path) // Create directory if it doesn't exist, returns bool
host_remove_dir(path) // Recursively remove directory, returns bool
// Screen reader
host_announce_screenreader(text) // Speak text via TTS (if screen reader enabled)
// Tool module lifecycle
host_exit_module() // Exit current tool module, return to tools menu (tool modules only)
// MIDI injection (inject MIDI into Move's firmware input path)
move_midi_inject_to_move([type, status, d1, d2]) // Simulate hardware MIDI input
// Sampler (record audio output to WAV)
host_sampler_start(path) // Start recording to the given WAV file path
host_sampler_stop() // Stop recording
host_sampler_is_recording() // Returns bool - true if currently recording
// Shadow-mode only functions (available in shadow_ui.js context)
host_system_cmd(cmd) // Execute allowlisted system command
host_wake_all_slots() // Clear idle flags on all shadow slots
host_mute_move_audio(bool) // Mute/unmute Move's audio output
host_http_download_background(url, dest) // Background HTTP download
host_sampler_set_external_stop(bool) // Set external stop flag for samplerhost_module_send_midi accepts a 3-byte array [status, data1, data2] and an optional source ("internal", "external", or "host").
host_load_ui_module returns a boolean and loads the file as an ES module without invoking globalThis.init.
host_exit_module is dynamically created for tool modules only — it is not available in the main host context.
exit() // Exit Move Anything, return to stock Move
console.log(msg) // Log to /data/UserData/move-anything/move-anything.log- Notes 68-99: 32 pads (bottom-left to top-right, 4 rows of 8)
- Notes 16-31: 16 step buttons
- CCs 40-43: 4 track buttons (reversed: CC43=Track1, CC40=Track4)
| CC | Control | Notes |
|---|---|---|
| 3 | Jog wheel click | 127 = pressed |
| 14 | Jog wheel rotate | 1-63 = CW, 65-127 = CCW |
| 49 | Shift | 127 = held |
| 50 | Menu | |
| 51 | Back | |
| 52 | Capture | |
| 54 | Down arrow | |
| 55 | Up arrow | |
| 62 | Left arrow | |
| 63 | Right arrow | |
| 71-78 | Knobs 1-8 | Relative encoder (1-63 CW, 65-127 CCW) |
| 79 | Master volume | Relative encoder (1-63 CW, 65-127 CCW) |
| 85 | Play | |
| 86 | Record | |
| 88 | Mute | |
| 118 | Record button | LED: RGB (use Note for input) |
| 119 | Delete |
Notes 0-9 are generated when knobs are touched. Filter these if you don't need them:
if (data[1] < 10) return; // Ignore capacitive touchWhen raw_midi is false, the host filters knob-touch notes from internal MIDI before modules receive them.
The volume knob (CC 79) controls host-level output volume by default:
- Volume is applied after DSP rendering, before audio output
- Range: 0-100 (default: 100)
- Displayed in host menu UI
- Use
host_get_volume()andhost_set_volume()to read/write from JS
Modules can claim the volume knob for their own use by setting "claims_master_knob": true in module.json. When claimed, the host passes CC 79 through to the module instead of adjusting volume.
The host provides MIDI input processing that can be configured from the Settings menu:
Applied to Note On messages before forwarding to modules:
| Curve | Behavior |
|---|---|
linear |
No transform (default) |
soft |
Boost low velocities: 64 + (velocity / 2) |
hard |
Exponential curve: (velocity * velocity) / 127 |
full |
Always 127 |
- Enabled/Disabled: When disabled, aftertouch messages are dropped
- Dead Zone (0-50): Values below threshold become 0, reducing accidental triggers
// Get current velocity curve
let curve = host_get_setting('velocity_curve'); // 'linear', 'soft', 'hard', 'full'
// Get aftertouch settings
let atEnabled = host_get_setting('aftertouch_enabled'); // 0 or 1
let atDeadzone = host_get_setting('aftertouch_deadzone'); // 0-50
// Modify settings
host_set_setting('velocity_curve', 'hard');
host_set_setting('aftertouch_deadzone', 15);
host_save_settings(); // Persist to diskModules that need unprocessed MIDI input can opt out of transforms by setting "raw_midi": true in module.json (this also disables the host knob-touch filter).
Modules that want full control of UI input can opt out of host Back-to-menu handling by setting "raw_ui": true in module.json.
Some modules (like Signal Chain) also use raw_midi to bypass their own MIDI filters (for example, allowing knob-touch notes through).
Overtake modules take full control of Move's UI. The host provides special handling for these modules.
The MIDI output buffer holds approximately 64 USB-MIDI packets. Sending more than ~60 LED commands in a single frame causes buffer overflow. Additionally, LED sends can be overwritten by Move's own firmware updating the same LEDs in the same frame. Use progressive LED handling with a queue-based approach:
Queue pattern: Instead of sending LED commands immediately, push them to a queue and drain a fixed number per tick. This prevents buffer overflow and improves reliability when competing with Move's own LED updates.
const LEDS_PER_FRAME = 8;
let ledInitIndex = 0;
let ledInitPending = true;
function setupLedBatch() {
const ledsToSet = [...]; // All LEDs you want to set
const start = ledInitIndex;
const end = Math.min(start + LEDS_PER_FRAME, ledsToSet.length);
for (let i = start; i < end; i++) {
setLED(ledsToSet[i].note, ledsToSet[i].color);
}
ledInitIndex = end;
if (ledInitIndex >= ledsToSet.length) {
ledInitPending = false;
}
}
globalThis.tick = function() {
if (ledInitPending) setupLedBatch();
drawUI();
};The host provides a built-in escape that works regardless of module behavior:
Shift + Volume Touch + Jog Click
The host tracks these inputs locally (independent of the shim) to ensure escape always works. Modules should not block or consume these inputs for other purposes.
| Input | Behavior |
|---|---|
| Shift (CC 49) | Tracked by host for escape; passed to module |
| Volume touch (Note 8) | Tracked by host for escape; passed to module |
| Jog click (CC 3) | If Shift+Vol held, triggers escape; otherwise passed to module |
| All other MIDI | Passed directly to module |
Modules receive full MIDI access and can send to both internal (LEDs) and external (USB-A) targets.
Audio is handled by native DSP plugins (.so files). See MODULES.md for plugin API.
- Sample rate: 44100 Hz
- Block size: 128 frames
- Format: Stereo interleaved int16 (L0, R0, L1, R1, ...)
Native plugins can access audio input through the host API:
// In your plugin's render_block function:
int16_t *audio_in = (int16_t *)(host->mapped_memory + host->audio_in_offset);
// audio_in contains 128 stereo samples in interleaved formatNote: Audio input routing depends on the last selected input in the stock Move interface before launching Move Anything.
When a DSP plugin runs inside Signal Chain, host_api_v1_t may expose optional modulation callbacks:
int (*mod_emit_value)(void *ctx,
const char *source_id,
const char *target,
const char *param,
float signal,
float depth,
float offset,
int bipolar,
int enabled);
void (*mod_clear_source)(void *ctx, const char *source_id);
void *mod_host_ctx;Use these to publish temporary modulation contributions without overwriting saved base parameter values. If callbacks are unavailable, plugins should fail silently and keep operating normally.
Common color values for pad LEDs (from constants.mjs):
const Black = 0; // 0x00
const White = 120; // 0x78
const LightGrey = 118; // 0x76
const Red = 127; // 0x7F
const Blue = 125; // 0x7D
const BrightGreen = 8; // 0x08
const BrightRed = 1; // 0x01See src/shared/constants.mjs for the full RGB color palette (0-127).