Skip to content

Custom Transform Logic

Advanced topic: Building custom MIDI processing beyond built-in nodes

Overview

Documentation note

Neurode MIDI v2 treats NeuroScript 2.0 source as the canonical encoding for routing/transforms. Visual authoring may exist, but it compiles to NeuroScript (no TransformGraph/DAG runtime requirement).

Lua scripting is supported, but is non-canonical in v2.

While Neurode MIDI provides built-in routing/transformation primitives, you may need custom logic for:

  • Algorithmic composition — Generative patterns, arpeggios, chord progressions
  • Adaptive processing — Context-aware transformations based on musical analysis
  • Hardware integration — Specialized controller mappings unique to your gear
  • Experimental effects — Custom algorithms not covered by standard nodes

In v2, custom processing is expressed as NeuroScript 2.0 (canonical) and compiled off-thread into an immutable execution plan.


Neuroscript is a musician-focused DSL optimized for common MIDI operations:

When to Use

  • Standard transforms (transpose, velocity scaling, filtering, remapping)
  • Readable, maintainable scripts for musicians
  • Fast prototyping without programming knowledge

Example: Custom Velocity Curve

neuroscript
# Compress loud notes, boost quiet notes
when vel > 100: vel scale 90..110
when vel < 40: vel scale 50..70
pass

Example: Conditional Layering

neuroscript
# Soft notes go to pad, loud notes to lead
when vel < 60: ch 1 -> ch 3
when vel >= 60: ch 1 -> ch 2
pass

Limitations

  • No state between events (each event processed independently)
  • No timing/delay (synchronous processing only)
  • Fixed operations (no custom math/randomization)

Full Reference: Neuroscript Language


Approach 2: Lua (Advanced / Non-canonical)

Lua is available as a scripting engine for advanced, stateful logic.

Lua is non-canonical (v2)

If you are building presets meant to be portable/auditable, prefer NeuroScript. Lua is best treated as a power-user escape hatch and may have stricter limits in v2.

When to Use

  • Stateful transforms where NeuroScript isn't enough yet (counters, probabilistic behaviour)
  • Experimental logic you don’t want to encode as built-ins
  • Prototyping an idea before it becomes a first-class NeuroScript feature

Example: Simple Velocity Accent

lua
-- Boost note-on velocity slightly
if event.type == "noteOn" then
  event.data2 = MIDI.clamp(event.data2 + 10)
end
return event

Example: Stateful Counter (Context Store)

lua
-- Count note-on events in this script instance (local scope)
if event.type == "noteOn" then
  local count = context.get("local", "count") or 0
  count = count + 1
  context.set("local", "count", count)
end
return event

Reference: Lua API


Comparison: Choosing the Right Approach

FeatureNeuroScriptLua
CanonicalYesNo (non-canonical)
DeterminismStrong (bounded plan)Depends on engine limits
Ease of UseVery easyMedium
State Between EventsLimited (depends on v2 features)Yes (via context)
Best ForStandard transforms, auditable presetsAdvanced/custom logic

Decision Matrix

Use Neuroscript if:

  • Your logic fits standard operations (transpose, filter, velocity, remap)
  • You want readable, maintainable scripts
  • You don't need state or randomization

Use Lua if:

  • You need stateful or probabilistic logic now
  • You accept that Lua scripts may be less portable than NeuroScript

Best Practices

1. Start with Built-In Nodes

Before writing custom scripts, check if built-in nodes can achieve the same result:

Prefer built-in transforms (transpose, velocity clamp, channel remap, etc.) when they exist.

Built-in nodes are faster, more maintainable, and visually clearer in the graph.

2. Keep Scripts Small and Focused

Good (focused script):

neuroscript
# Single responsibility: boost quiet notes
when vel < 50: vel scale 60..80
pass

If a script grows beyond ~10–15 lines, consider splitting it into separate routes/presets or refactoring into smaller, commented sections.

3. Test Scripts in Isolation

Use the Playground Simulator to test scripts before embedding in routes:

  1. Write script
  2. Test with sample MIDI events
  3. Verify output
  4. Apply the script to your route/preset

4. Profile Script Performance

Add Monitor nodes before/after script nodes to measure latency:

[Input] → [Monitor A] → [Script] → [Monitor B] → [Output]

If latency exceeds 1ms consistently, consider:

  • Simplifying logic
  • Using built-in nodes
  • Switching to Lua

5. Handle Edge Cases

MIDI events can be unpredictable — always validate:

lua
-- Clamp notes after a transpose
if event.type == "noteOn" or event.type == "noteOff" then
    event.data1 = MIDI.clamp(event.data1 + 12)
end
return event

6. Comment Your Logic

Future you (or other musicians) will thank you:

lua
-- Humanize velocity slightly (noteOn only)
if event.type == "noteOn" then
    local delta = (math.random() * 10) - 5
    event.data2 = MIDI.clamp(math.floor(event.data2 + delta))
end
return event

Advanced Patterns

These patterns are often better expressed as NeuroScript once v2 scheduling/tags are implemented. Until then, Lua can be used for prototyping.


Limitations & Future Roadmap

Current Limitations

  • No native Swift API: Cannot create custom node types in Swift (script nodes only)
  • No async operations: Scripts run synchronously (no network, file I/O, setTimeout)
  • No persistent storage: State resets when script reloads
  • No inter-node communication: Nodes cannot share state
  • No timing control: Cannot schedule future events within script

Future: Native Swift Extension API (Planned)

A future Swift API will enable:

  • Custom node types: Build nodes with native performance
  • Visual parameters: Expose controls in the graph UI
  • Compile-time optimization: Swift nodes compiled with app
  • Reusable components: Package and share custom nodes

When available, native nodes will be preferred over scripts for:

  • Core transform operations
  • Performance-critical paths
  • Reusable, well-defined functionality

Scripts will remain ideal for:

  • Prototyping and experimentation
  • User-specific custom logic
  • One-off transforms unique to a setup

Migration Path: Script → Native Node (Future)

Once the Swift extension API is available:

  1. Prototype in script (fast iteration, immediate testing)
  2. Validate with users (confirm behaviour is correct)
  3. Implement as native node (performance + reusability)
  4. Replace script node (keep script version for reference)

This workflow balances rapid prototyping with long-term performance.


For ready-to-run examples, use the in-app Script Engine “Examples” menu.


Next Steps


Custom transforms are part of Neurode MIDI, a MIDI routing and transformation system for iOS and macOS.

Built with ❤️ for musicians