Skip to content

doctorsolberg/teensy-midi-cc-router-remapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Teensy 4.1 USB MIDI CC Router

A flexible, low-latency USB MIDI Control Change router running on a Teensy 4.1. Route, remap, and fan-out incoming CC messages between 4 virtual USB MIDI cables using a simple JSON config file on an SD card — no recompiling required.


The Problem This Solves

If you use the MRCC (MIDI Router Control Center) by Conducive Labs in a dawless setup, you know its limitation: it only provides 6 mod slots for CC remapping. When working with multiple controllers and multi-timbral synthesizers this quickly becomes a hard ceiling — especially if you want to map faders or knobs to specific parameters across many tracks.

The typical workaround is a laptop running a DAW or MIDI utility software. This Teensy-based router eliminates that need entirely. It plugs into the MRCC as a standard USB MIDI device and handles unlimited CC remapping internally, with switchable pages of routes so a single 8-fader controller can address 16, 32, or more synthesizer tracks — no computer, no DAW, no compromise.

How it fits in the signal chain

MIDI Controllers
      │
      ▼
   [ MRCC ]  ←─── USB ───→  [ Teensy MIDI Router ]  (this project)
      │                             ↑
      │              remaps CCs, switches pages
      ▼
  Synthesizers

The MRCC routes raw controller output into the Teensy over USB. The Teensy remaps CC numbers and channels according to the JSON config, then sends the transformed messages back to the MRCC, which forwards them to the target synthesizers. The Teensy is fully self-contained: config lives on its SD card, page switching is done via CC messages from the controller, and the built-in LED confirms the active page with blinks.


Inspiration

This project was inspired by the RouteCC Mozaic script by wim — a Mozaic (iOS AUv3 MIDI scripting) patch that implements the same concept of unlimited CC remapping with switchable pages. RouteCC proves the idea works beautifully; this project brings the same capability to a standalone hardware device for fully dawless use.


Features

  • CC remapping — route any CC number on any channel to a different CC number and/or channel on a different virtual cable
  • 4 virtual USB MIDI cables — the Teensy appears as a single USB MIDI device with 4 independent ports
  • Wildcard matching — use "*" to match any channel or any CC number, and to forward source values unchanged to the destination
  • Fan-out routing — a single incoming message can trigger multiple output routes simultaneously
  • Multiple routing tables per cable — define up to 8 independent route sets per cable and switch between them live using CC messages from your controller
  • Table switching via CC — dedicate two CC numbers to page up/down through tables; these CCs are consumed by the router and never forwarded to synthesizers
  • LED table indication — the built-in LED blinks N times when you switch to a table (1 blink = table 1, 2 blinks = table 2, …)
  • Per-cable pass-through control — configure whether unrouted messages are forwarded or silently dropped, per cable
  • SD card hot-reload — edit the config on your computer, eject and reinsert the card; the router reloads automatically within 1 second
  • LED error feedback — visual indication of config problems without needing a serial monitor
  • Non-blocking main loop — MIDI processing, SD polling, and LED updates never block each other

Hardware Requirements

  • Teensy 4.1 — built-in SDIO SD card slot is used directly; no wiring required
  • SD card — any standard microSD card for the config file
  • USB cable — connect Teensy to the host computer as a USB MIDI device

Software Setup

Arduino IDE

  1. Install Teensyduino for Arduino IDE
  2. Set Tools → Board → Teensy 4.1
  3. Set Tools → USB Type → MIDI x4
  4. Set Tools → CPU Speed → 600 MHz (default)

Dependencies

Install via Sketch → Include Library → Manage Libraries:

  • ArduinoJson v7 by Benoit Blanchon

SD Card Setup

Format the SD card as FAT32 and place config.json in the root directory.

The router loads the config on boot. If no card is present or the file is missing, the LED indicates the error state and the router waits. Insert a valid card and it reloads automatically.


Config File Format

The config file is /config.json on the SD card root.

Top-level structure

{
  "cable_0": { ... },
  "cable_1": { ... },
  "cable_2": { ... },
  "cable_3": { ... }
}

Cable sections are optional. Missing cables default to pass_unmatched: true with no routes (everything passes through unchanged).

Cable section fields

Field Type Default Description
pass_unmatched boolean true If true, CC messages that match no route are forwarded unchanged on the same cable. If false, they are silently dropped. Also controls non-CC message forwarding (Note On/Off, Pitch Bend, etc.).
switch_ch integer 1–16 MIDI channel the table-switch CCs must arrive on. Required if using table switching.
switch_prev_cc integer 0–127 CC number that selects the previous table (triggers on value > 0).
switch_next_cc integer 0–127 CC number that selects the next table (triggers on value > 0).
table_0, table_1, … array Named routing tables (up to 8 per cable). Only one is active at a time.

Note: switch_ch, switch_prev_cc, and switch_next_cc must all be specified together or not at all. Partial specification is a validation error.

Route syntax

Each table is a JSON array of routes. Each route is a 5-element array:

[src_ch, src_cc, dst_cable, dst_ch, dst_cc]
Position Field Valid values Wildcard "*" meaning
0 src_ch 1–16 or "*" Match any incoming MIDI channel
1 src_cc 0–127 or "*" Match any CC number
2 dst_cable 0–3 No wildcard — destination cable must be explicit
3 dst_ch 1–16 or "*" Use source channel unchanged
4 dst_cc 0–127 or "*" Use source CC number unchanged

Routing rules

  • Fan-out: every route that matches an incoming message fires. A single CC can produce output on multiple cables/channels simultaneously.
  • Unmatched messages: if no route matches, the pass_unmatched setting decides whether the original message is forwarded or dropped.
  • Table switching: switch_prev_cc and switch_next_cc wrap around (last table → next goes to table 0, and vice versa). Switch CCs are always consumed — they are never routed or passed through to synthesizers. CC value 0 (button release) is also consumed but does not change the active table.
  • Active table resets to table_0 whenever the config is reloaded.
  • Tables must be numbered contiguously starting from table_0. A gap (e.g., table_0 and table_2 without table_1) will cause table_2 and beyond to be ignored.

Comments

JSON does not support comments. Use "_comment" keys anywhere in the object — the router ignores all keys it does not recognise.


Full Config Example

{
  "_comment": [
    "Route syntax: [src_ch, src_cc, dst_cable, dst_ch, dst_cc]",
    "Use \"*\" for wildcards. Switch CCs are consumed and never forwarded."
  ],

  "cable_0": {
    "_comment": "SMC Mixer -> Digitone II — Mod & Breath, two track banks",
    "pass_unmatched": false,
    "switch_ch":      1,
    "switch_prev_cc": 104,
    "switch_next_cc": 105,

    "_table_0": "Tracks 1-8 : modwheel (CC1) and breath (CC2)",
    "table_0": [
      [1,  9, 0,  1, 1],
      [2, 19, 0,  2, 1],
      [3, 25, 0,  3, 1],
      [4, 31, 0,  4, 1],
      [5, 49, 0,  5, 1],
      [6, 55, 0,  6, 1],
      [7, 61, 0,  7, 1],
      [8, 82, 0,  8, 1],
      [1,  3, 0,  1, 2],
      [2, 18, 0,  2, 2],
      [3, 24, 0,  3, 2],
      [4, 30, 0,  4, 2],
      [5, 48, 0,  5, 2],
      [6, 54, 0,  6, 2],
      [7, 60, 0,  7, 2],
      [8, 81, 0,  8, 2]
    ],

    "_table_1": "Tracks 9-16 : modwheel (CC1) and breath (CC2)",
    "table_1": [
      [1,  9, 0,  9, 1],
      [2, 19, 0, 10, 1],
      [3, 25, 0, 11, 1],
      [4, 31, 0, 12, 1],
      [5, 49, 0, 13, 1],
      [6, 55, 0, 14, 1],
      [7, 61, 0, 15, 1],
      [8, 82, 0, 16, 1],
      [1,  3, 0,  9, 2],
      [2, 18, 0, 10, 2],
      [3, 24, 0, 11, 2],
      [4, 30, 0, 12, 2],
      [5, 48, 0, 13, 2],
      [6, 54, 0, 14, 2],
      [7, 60, 0, 15, 2],
      [8, 81, 0, 16, 2]
    ]
  },

  "cable_1": {
    "_comment": "Mirror everything from cable 1 back to cable 0",
    "pass_unmatched": false,
    "table_0": [
      ["*", "*", 0, "*", "*"]
    ]
  },

  "cable_2": {
    "_comment": "Pass everything through unchanged (default behavior)",
    "pass_unmatched": true
  }
}

What the example does

Cable 0 maps an 8-fader mixer to a 16-track synthesizer. CC messages arrive from the mixer on channels 1–8. Two tables map those faders to tracks 1–8 and 9–16 respectively. Pressing the button assigned to CC 105 on channel 1 switches to the next bank; CC 104 goes back. The LED blinks once for bank 1, twice for bank 2.

Cable 1 mirrors all incoming CC messages to cable 0, keeping the original channel and CC number.

Cable 2 passes all messages through without any routing rules.


LED Status Reference

LED behaviour Meaning
Off Normal operation — config loaded successfully
Slow blink (500 ms) No SD card detected, or config.json not found
Fast blink (100 ms) Config parse error or validation error — check the JSON
N short blinks (150 ms on / 100 ms gap) Table switched — blink count = active table number (1-based)

The table-switch blinks override the off state briefly, then the LED returns to off. Error blinks (slow/fast) are only set during config load and are not interrupted by table switching.


Limits

Parameter Limit
Virtual USB MIDI cables 4
Routing tables per cable 8
Routes per table 64
Total routes 2048 (4 cables × 8 tables × 64 routes)

All route data fits in approximately 10 KB of RAM. The Teensy 4.1 has 1024 KB available.


Project Structure

midi_router/
  midi_router.ino   — Main sketch: setup(), loop(), SD hot-reload
  config.h          — Data structures and constants
  config.cpp        — JSON parsing and validation (ArduinoJson v7)
  router.h          — Function declarations for CC handling
  router.cpp        — Route matching, fan-out dispatch, table switching
  led.h             — LED function declarations
  led.cpp           — Non-blocking LED state machine
  config.json       — Example config (copy to SD card root)

License

MIT

About

Teensy 4.1 USB MIDI CC Router and Mapper (for Condictive Labs MRCC)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors