Skip to content

Transform Pipeline (Legacy) โ€‹

Legacy documentation

This page describes an early visual node-graph idea ("Transform Pipeline").

Neurode MIDI v2 uses NeuroScript 2.0 source as the canonical encoding for routing/transforms.

  • Compilation happens off realtime threads into an immutable plan.
  • Plan activation is an atomic swap; failures fall back to pass-through and the UI shows the compile error.
  • There is no TransformGraph/DAG runtime requirement.

If you arrived here from other docs, treat the remainder of this page as historical.

Overview โ€‹

In the legacy design, instead of simple linear routing (Source โ†’ Destination), the Transform Pipeline allowed sophisticated processing chains:

[MIDI In] โ†’ [Filter] โ†’ [Transpose] โ†’ [Split] โ†’ [Output 1]
                                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ†’ [Output 2]

Each node processes MIDI events, and connections (edges) define the flow between nodes.


Core Concepts โ€‹

Nodes โ€‹

Nodes are processing units. Each node has:

  • Type: Determines behaviour (filter, transform, script, etc.)
  • Inputs/Outputs: Connection points (ports)
  • Parameters: Configuration specific to the node type
  • Enabled State: Can be bypassed without deletion

Connections (Edges) โ€‹

Connections link nodes together:

  • Source: Output port of one node
  • Target: Input port of another node
  • One input per port: Each input can only receive from one source
  • Multiple outputs: An output can fan out to many nodes

Graphs โ€‹

A graph is a complete pipeline:

  • Collection of nodes
  • Set of connections
  • Metadata (name, creation date, etc.)

Node Categories โ€‹

1. Input/Output โ€‹

MIDI Input โ€” Entry point for incoming MIDI events

  • Inputs: None
  • Outputs: 1 (all events)
  • Use: Automatically created for each route

MIDI Output โ€” Exit point sending events to destination

  • Inputs: 1 (events to send)
  • Outputs: None
  • Use: Automatically created for each route

Virtual Input/Output โ€” For internal routing

  • Planned feature for complex multi-route setups

2. Filters โ€‹

Filters select which events pass through:

Note Filter โ€” Pass/block note events

  • Parameters: Note range (e.g., C3-C5), pass/block mode
  • Use: Isolate specific keyboard zones

Channel Filter โ€” Pass/block by MIDI channel

  • Parameters: Channels 1-16 selection, pass/block mode
  • Use: Route specific channels differently

Velocity Filter โ€” Filter by note velocity

  • Parameters: Velocity range (0-127), pass/block mode
  • Use: Separate loud/soft playing layers

CC Filter โ€” Filter Control Change messages

  • Parameters: CC number range, pass/block mode
  • Use: Remove unwanted CC data

Message Type Filter โ€” Filter by event type

  • Parameters: Note, CC, Program Change, Pitch Bend, etc.
  • Use: Drop all CC, keep only notes

Realtime Filter โ€” Filter clock and system messages

  • Parameters: Clock, Start, Stop, Continue
  • Use: Clean up clock spam

3. Transforms โ€‹

Transforms modify event data:

Transpose โ€” Shift note pitch

  • Parameters: Semitones (+/- 127), clamp/wrap/drop mode
  • Use: Octave shifts, key changes

Octave Shift โ€” Convenience wrapper for transpose by octaves

  • Parameters: Octaves (+/- 10), mode
  • Use: Same as transpose, but in octave units

Note Map โ€” Remap individual notes

  • Parameters: List of note pairs (from โ†’ to)
  • Use: Scale corrections, alternate tunings

Velocity Curve โ€” Apply curve to velocity

  • Parameters: Curve type (linear, exponential, logarithmic, custom)
  • Use: Shape dynamics naturally

Velocity Scale โ€” Map velocity to range

  • Parameters: Min/max output range
  • Use: Boost quiet notes, tame loud ones

Fixed Velocity โ€” Set constant velocity

  • Parameters: Fixed value (0-127)
  • Use: Remove dynamics entirely

Channel Remap โ€” Change MIDI channel

  • Parameters: From channel, to channel
  • Use: Route drums to melodic channel, etc.

CC Remap โ€” Change CC number

  • Parameters: From CC, to CC
  • Use: Adapt controllers for different synths

CC Value Scale/Curve โ€” Modify CC values

  • Parameters: Range or curve
  • Use: Adjust modulation response

4. Generators โ€‹

Generators create additional events:

Chord โ€” Add chord notes (planned)

  • Parameters: Chord type, voicing
  • Use: Harmonize single-note input

Harmonizer โ€” Add harmonies (planned)

  • Parameters: Interval, scale
  • Use: Automatic vocal-style harmonies

Arpeggiator โ€” Generate arpeggios (planned)

  • Parameters: Pattern, tempo
  • Use: Convert chords to arpeggios

Echo โ€” Delayed repetitions (planned)

  • Parameters: Delay time, feedback, decay
  • Use: MIDI delay effect

Strum โ€” Humanize chord timing (planned)

  • Parameters: Strum speed, direction
  • Use: Guitar-like chord rolls

5. Logic โ€‹

Logic nodes control event flow:

Split โ€” Duplicate events to multiple outputs

  • Inputs: 1
  • Outputs: Multiple (configurable)
  • Use: Send to multiple processing chains

Merge โ€” Combine events from multiple inputs

  • Inputs: Multiple (configurable)
  • Outputs: 1
  • Use: Recombine split paths

Switch โ€” Route to one of several outputs

  • Parameters: Active output index, control source
  • Use: Dynamic routing based on condition

Gate โ€” Pass/block events dynamically

  • Parameters: Open/closed state, control source
  • Use: Mute/unmute sections

Conditional โ€” Route based on event properties

  • Parameters: Condition expression
  • Use: "If velocity > 90, go left; else go right"

6. Utility โ€‹

Utility nodes provide debugging and optimization:

Monitor โ€” Observe event stream

  • Parameters: Display mode
  • Use: Debugging, visualizing flow

Latency โ€” Measure/compensate latency (planned)

  • Parameters: Compensation amount
  • Use: Align timing across routes

Quantize โ€” Snap note timing (planned)

  • Parameters: Grid resolution
  • Use: Tighten timing to grid

Humanize โ€” Add timing/velocity variation (planned)

  • Parameters: Amount, randomness
  • Use: Natural feel

Panic โ€” Send all-notes-off

  • Parameters: Trigger source
  • Use: Emergency stop

7. Script Nodes โ€‹

Script nodes run custom code:

Neuroscript โ€” Line-based DSL

Lua โ€” Lightweight scripting

  • Parameters: Lua code
  • Use: Performance-critical custom code

Building a Pipeline โ€‹

Step 1: Start with a Template โ€‹

Every route begins with a default pipeline:

[MIDI Input] โ†’ [MIDI Output]

This is a passthrough โ€” events flow directly from source to destination.

Step 2: Insert Nodes โ€‹

Add nodes between input and output:

  1. Open the Transform Pipeline editor for the route
  2. Click Add Node (+ button)
  3. Select node type from category
  4. Place node on canvas
  5. Connect by dragging from output port to input port

Example: Add transpose node:

[MIDI Input] โ†’ [Transpose] โ†’ [MIDI Output]

Step 3: Configure Parameters โ€‹

Select the node and adjust parameters:

  • Transpose: Set semitones to +12 (one octave up)
  • Mode: Choose "clamp" (safe for live performance)

Step 4: Test with Monitor โ€‹

Add a Monitor node to observe event flow:

[MIDI Input] โ†’ [Monitor] โ†’ [Transpose] โ†’ [MIDI Output]

Monitor shows events passing through in real-time.

Step 5: Add Complexity โ€‹

Insert more nodes, create branches:

[MIDI Input] โ†’ [Channel Filter: CH 1] โ†’ [Transpose +12] โ†’ [Output 1]
               [Channel Filter: CH 2] โ†’ [Transpose -12] โ†’ [Output 2]

This splits channel 1 (up octave) and channel 2 (down octave) to different destinations.


Execution Model โ€‹

Topological Sort โ€‹

Graphs execute in topological order โ€” nodes are processed based on dependencies, not visual position:

A โ†’ B โ†’ D
A โ†’ C โ†’ D

Execution order: A โ†’ (B, C in parallel) โ†’ D

Pipeline Execution Flow โ€‹

mermaid
graph LR
    A[MIDI Input] --> B[Note Filter]
    B --> C[Transpose +12]
    C --> D[Velocity Scale]
    D --> E[Script Node]
    E --> F[MIDI Output]
    
    style A fill:#10b981,stroke:#059669,stroke-width:2px,color:#fff
    style B fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff
    style C fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style D fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style E fill:#ec4899,stroke:#db2777,stroke-width:2px,color:#fff
    style F fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff

Node Types: Input (green), Filters (orange), Transforms (purple), Scripts (pink), Output (blue). Events flow left-to-right through the pipeline.

Cycle Detection โ€‹

Graphs cannot contain cycles โ€” feedback loops are invalid:

A โ†’ B โ†’ C
    โ†‘   โ†“
    โ””โ”€โ”€โ”€โ”˜

This would create infinite loop and is rejected at validation.

Event Flow โ€‹

  1. MIDI event arrives at MIDI Input node
  2. Event is passed to connected nodes
  3. Each node processes and optionally transforms the event
  4. Processed event(s) continue to next nodes
  5. Final events exit through MIDI Output node

Key: Nodes can:

  • Drop events (filters blocking)
  • Pass unchanged (transparent)
  • Transform (modify properties)
  • Generate (create additional events)

Parallel Branches โ€‹

When a node outputs to multiple connections, events are duplicated:

[Input] โ†’ [Split] โ†’ [Transpose +12] โ†’ [Output 1]
                 โ†’ [Transpose -12] โ†’ [Output 2]

Same event processed twice, each output gets independent copy.

Branching Pipeline Visualization โ€‹

mermaid
graph TD
    A[MIDI Input] --> B[Split Node]
    B --> C[Transpose +12]
    B --> D[Transpose -12]
    C --> E[Velocity Scale 80-127]
    D --> F[Velocity Scale 40-80]
    E --> G[Output 1: Lead Synth]
    F --> H[Output 2: Bass Synth]
    
    style A fill:#10b981,stroke:#059669,stroke-width:2px,color:#fff
    style B fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff
    style C fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style D fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style E fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style F fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style G fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff
    style H fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff

Split Node duplicates events to both branches. Each branch applies independent transforms before reaching separate outputs.


Example Pipelines โ€‹

1. Lead Zone with Boost โ€‹

Goal: Keep notes on channel 1, transpose up, tame loud notes.

[MIDI Input]
    โ†“
[Channel Filter: CH 1]
    โ†“
[Transpose: +12 clamp]
    โ†“
[Velocity Curve: Compress]
    โ†“
[MIDI Output]

Neuroscript Equivalent:

neuroscript
keep note, ch 1
transpose +12 clamp
vel clamp 50..110
pass

2. Keyboard Split (Bass + Lead) โ€‹

Goal: Low keys โ†’ bass synth, high keys โ†’ lead synth.

[MIDI Input]
    โ†“
[Split]
    โ”œโ”€โ†’ [Note Filter: C1-B3] โ†’ [Channel Remap: CH 1โ†’2] โ†’ [Output: Bass]
    โ””โ”€โ†’ [Note Filter: C4-C7] โ†’ [Channel Remap: CH 1โ†’3] โ†’ [Output: Lead]

3. Velocity Layer Routing โ€‹

Goal: Soft notes to pad, loud notes to lead.

[MIDI Input]
    โ†“
[Split]
    โ”œโ”€โ†’ [Velocity Filter: 0-70] โ†’ [Output: Pad Synth]
    โ””โ”€โ†’ [Velocity Filter: 71-127] โ†’ [Output: Lead Synth]

4. MIDI Cleanup (Remove Clock) โ€‹

Goal: Strip clock messages, standardize CC.

[MIDI Input]
    โ†“
[Realtime Filter: Block Clock/Start/Stop]
    โ†“
[CC Remap: 1โ†’74]
    โ†“
[MIDI Output]

5. Dynamic Harmonizer (Future) โ€‹

Goal: Add harmony based on velocity.

[MIDI Input]
    โ†“
[Split]
    โ”œโ”€โ†’ [Pass Through] โ†’ [Merge] โ†’ [Output]
    โ””โ”€โ†’ [Conditional: vel > 90]
            โ†“
        [Harmonizer: +7 semitones] โ†’ [Merge]

When velocity exceeds 90, add harmony a fifth above.


Linearization โ€‹

Some graphs can be linearized โ€” represented as a simple ordered list of steps (no branching).

Linear Graph โ€‹

[Input] โ†’ [A] โ†’ [B] โ†’ [C] โ†’ [Output]

This linearizes to: [A, B, C] โ€” simple sequential processing.

Non-Linear Graph (Branching) โ€‹

[Input] โ†’ [Split] โ†’ [A] โ†’ [Merge] โ†’ [Output]
                 โ†’ [B] โ”€โ”€โ”€โ”€โ”€โ”˜

This cannot linearize โ€” requires full graph execution.

Why Linearization Matters โ€‹

  • Simple Editor: Linear graphs can use the simple transform list UI
  • Performance: Linear execution is slightly faster
  • Debugging: Easier to understand sequential flow

Preference: Keep graphs linear when possible. Use branching only when necessary.


Performance Considerations โ€‹

Node Count โ€‹

  • Target: <10 nodes per route for real-time performance
  • Max: 50 nodes supported, but latency increases

Script Node Costs โ€‹

  • Neuroscript: Fast (compiled)
  • Lua: Fast (lightweight interpreter)

Recommendation: Prefer built-in nodes over scripting when available.

Monitor Node Impact โ€‹

Monitor nodes add minimal overhead but should be disabled in production:

  • Enable for debugging
  • Disable for live performance

Parallel Branches โ€‹

Branches execute sequentially, not truly parallel:

[Split] โ†’ [A (10ฮผs)] โ†’ [Merge]
       โ†’ [B (5ฮผs)]  โ”€โ”€โ”˜

Total: ~15ฮผs, not 10ฮผs.

Keep branch processing lightweight.


Graph Validation โ€‹

Graphs are validated before execution:

Must Have โ€‹

  • At least one MIDI Input node
  • At least one MIDI Output node
  • All nodes connected (no orphans)

Cannot Have โ€‹

  • Cycles (A โ†’ B โ†’ A)
  • Disconnected subgraphs (islands)
  • Invalid connections (type mismatches)

Validation errors appear in the editor with specific messages and highlights.


Debugging Pipelines โ€‹

1. Use Monitor Nodes โ€‹

Insert Monitor nodes at key points to observe event flow:

[Input] โ†’ [Monitor: "After Filter"] โ†’ [Filter] โ†’ [Monitor: "After Transform"] โ†’ [Output]

2. Enable/Disable Nodes โ€‹

Toggle enabled state to isolate issues:

  • Disable suspect node โ†’ Does problem persist?
  • If yes, issue is elsewhere
  • If no, issue is in that node

3. Check Event Visualizer โ€‹

The global Event Visualizer shows final output events โ€” use it to confirm pipeline results.

4. Simplify โ€‹

If pipeline misbehaves:

  1. Remove all nodes except input/output (passthrough)
  2. Add nodes back one at a time
  3. Test after each addition
  4. Isolate the failing node

5. Compare to Neuroscript โ€‹

If you have a working Neuroscript version, create equivalent graph and compare:

  • Neuroscript is easier to debug (line-by-line)
  • Graph is more flexible but harder to trace

Presets โ€‹

Pipelines can be saved as Presets and reused:

Saving โ€‹

  1. Create a working pipeline
  2. Click Save as Preset
  3. Name it (e.g., "Bass Zone Boost")
  4. Optionally add description/tags

Loading โ€‹

  1. Open route's Transform Pipeline editor
  2. Click Load Preset
  3. Select from library
  4. Pipeline is replaced

Sharing (Future) โ€‹

Presets will be exportable/importable for sharing with other users.


Best Practices โ€‹

1. Keep It Simple โ€‹

Start with the simplest pipeline that works. Add complexity only when needed.

Good:

[Input] โ†’ [Transpose] โ†’ [Output]

Overkill (for simple transpose):

[Input] โ†’ [Split] โ†’ [Transpose A] โ†’ [Merge] โ†’ [Output]
                 โ†’ [Transpose B] โ”€โ”€โ”€โ”€โ”€โ”˜

2. Name Your Nodes โ€‹

Default names like "Transpose 1" become confusing in complex graphs:

  • Bad: "Transpose 1", "Transpose 2"
  • Good: "Bass Up Octave", "Lead Down Fifth"

3. Use Comments (Future) โ€‹

Once annotation support is added, document why a node exists:

  • "Clamps velocity because XYZ synth clips above 110"
  • "Removes CC 64 which causes stuck sustain on ABC hardware"

4. Modular Design โ€‹

Break complex pipelines into multiple routes instead of one giant graph:

  • Route 1: Input โ†’ Cleanup โ†’ Virtual Out
  • Route 2: Virtual In โ†’ Processing โ†’ Final Out

Easier to debug and reuse.

5. Test Incrementally โ€‹

Build pipelines one node at a time:

  1. Add node
  2. Test immediately
  3. Confirm correct behaviour
  4. Proceed to next node

Don't build entire graph then debug โ€” you won't know where the issue is.

6. Monitor Key Points โ€‹

Place Monitor nodes at:

  • After filters (confirm events are filtered correctly)
  • Before scripts (see input to custom code)
  • Before output (final event check)

7. Disable, Don't Delete โ€‹

When experimenting, disable nodes instead of deleting:

  • Preserve your work
  • Easy to re-enable and compare
  • No need to remember parameters

Limitations โ€‹

No Feedback Loops โ€‹

Graphs cannot contain cycles:

A โ†’ B โ†’ C
    โ†‘   โ†“
    โ””โ”€โ”€โ”€โ”˜  โŒ Invalid

For delay effects, use dedicated Echo node (planned).

No Timing Control โ€‹

Nodes process events immediately โ€” no scheduling or delays within graph.

For timing effects:

  • Use multiple routes with routing-level timing (future)
  • Use scripting with internal state (limited)

No Conditional Branching (Yet) โ€‹

Current Split node duplicates to all outputs. Conditional node (planned) will enable:

[Input] โ†’ [Conditional: vel > 90] โ†’ [Output A]
                                  โ†’ [Output B (else)]

No External State โ€‹

Nodes cannot share state between processing:

  • Each event processed independently
  • No "remember last note" or cross-event logic (except in scripts)

Advanced: Custom Node Types โ€‹

Currently, custom nodes are not supported via Swift API.

For custom logic, use Script nodes (Neuroscript, Lua).

Future: Custom Swift-based node types with native performance.


Comparison: Pipeline vs. Simple Routing โ€‹

FeatureSimple RoutingTransform Pipeline
Ease of UseEasyMedium
FlexibilityLimitedHigh
BranchingNoYes
VisualList-basedNode graph
PerformanceFastestVery fast
Best ForBasic routesComplex processing

When to use Simple Routing:

  • Straightforward MIDI passthrough
  • Single transform (transpose, channel remap)

When to use Transform Pipeline:

  • Multiple transforms in sequence
  • Branching/splitting
  • Conditional logic
  • Debugging complex processing

Next Steps โ€‹


This legacy Transform Pipeline concept was an early exploration for Neurode MIDI (iOS/macOS).

Built with โค๏ธ for musicians