Skip to content

Instantly share code, notes, and snippets.

@ravengraves
Created September 25, 2026 04:57
Show Gist options
  • Select an option

  • Save ravengraves/309c142fe75c57bbaa867f5a9fcc75cf to your computer and use it in GitHub Desktop.

Select an option

Save ravengraves/309c142fe75c57bbaa867f5a9fcc75cf to your computer and use it in GitHub Desktop.
reaper n-tet midi remapper
desc:Variable-TET Note Mapper (MPE)
// Input MIDI note = TET step index.
// Input note 0 maps to the selected base MIDI note.
// Uses MPE member channels 2-16.
// Supports only up to 16 notes at a time.
// match pitch bend range with synth; usually 2.0
slider1:2<1,12,1>Pitch Bend Range (semitones).
slider2:16<1,72,1>TET Divisions per Octave
slider3:21<0,127,1>Base MIDI Note (21 = A0, 33=A1)
@init
// Memory layout
ch_note = 0; // 16 entries
note_ch = 16; // 128 entries
active_out = 144; // 128 entries
vel = 272; // 128 entries
ring_act = 400; // 72 entries
ring_note = 472; // 72 entries
i = 0;
loop(16,
ch_note[i] = -1;
i += 1;
);
i = 0;
loop(128,
note_ch[i] = -1;
active_out[i] = -1;
vel[i] = 0;
ring_act[i] = 0;
ring_note[i] = 0;
i += 1;
);
@slider
// Precompute step sizes
tet_div = max(1, floor(slider2 + 0.5));
step = 12 / tet_div;
pb_scale = 8192 / max(1, floor(slider1 + 0.5));
base = floor(slider3 + 0.5);
@block
while (midirecv(offset, msg1, msg2, msg3)) (
status = msg1 & 0xF0;
// NOTE ON
status == 0x90 && msg3 > 0 ? (
n = msg2;
vel[n] = msg3;
// Compute desired 12-TET MIDI-coordinate pitch.
target = base + n * step;
// Nearest MIDI note.
out = floor(target + 0.5);
out = max(0, min(127, out));
// Remaining fractional semitone becomes pitch bend.
pb = floor(
8192 + (target - out) * pb_scale + 0.5
);
pb = max(0, min(16383, pb));
// Retrigger existing instance.
old_ch = note_ch[n];
old_ch > 0 ? (
midisend(offset, 0x80 | old_ch, active_out[n], 0);
ch_note[old_ch] = -1;
note_ch[n] = -1;
active_out[n] = -1;
);
// Find free MPE channel.
out_ch = -1;
c = 1;
count = 0;
while (count < 15 && out_ch < 0) (
ch_note[c] < 0 ? out_ch = c;
c += 1;
c > 15 ? c = 1;
count += 1;
);
// Drop if all are occupied.
out_ch > 0 ? (
ch_note[out_ch] = n;
note_ch[n] = out_ch;
active_out[n] = out;
midisend(
offset,
0xE0 | out_ch,
pb & 127,
(pb >> 7) & 127
);
midisend(
offset,
0x90 | out_ch,
out,
msg3
);
) : (
vel[n] = 0;
);
// NOTE OFF
) : (
status == 0x80 ||
(status == 0x90 && msg3 == 0)
) ? (
n = msg2;
vel[n] = 0;
out_ch = note_ch[n];
out_ch > 0 ? (
midisend(
offset,
0x80 | out_ch,
active_out[n],
msg3
);
ch_note[out_ch] = -1;
note_ch[n] = -1;
active_out[n] = -1;
);
// OTHER MIDI — only the master channel (1) and system messages are
// still valid after we've remapped notes onto member channels 2-16.
// Forwarding member-channel PB/CC would retune whichever note we
// placed on that output channel (e.g. the bass).
) : (
(status >= 0xF0 || (msg1 & 0x0F) == 0) ? midisend(offset, msg1, msg2, msg3);
);
);
// RING VISUALIZATION
@gfx
gfx_set(0,0,0,1); gfx_rect(0,0,gfx_w,gfx_h);
t = tet_div;
cx = gfx_w * 0.5; cy = gfx_h * 0.5; r = min(gfx_w, gfx_h) * 0.35;
gfx_set(0.25,0.25,0.25,1); gfx_circle(cx,cy,r,0);
i = 0; loop(t,
a = 2*$pi*i/t - $pi/2;
gfx_set(0.35,0.35,0.35,1);
gfx_line(cx+cos(a)*r*0.92,cy+sin(a)*r*0.92, cx+cos(a)*r,cy+sin(a)*r);
i += 1;
);
// Scan all 128 notes, aggregate by ring position
i = 0; loop(t, ring_act[i]=0; ring_note[i]=0; i += 1;);
i = 0; loop(128,
vel[i] > 0 ? (
p = i % t;
ring_act[p] = 1;
i > ring_note[p] ? ring_note[p] = i;
);
i += 1;
);
// Lines between successive active ring positions (modulo wrap)
first = -1; prev = -1; last = -1;
i = 0; loop(t,
ring_act[i] ? (
first < 0 ? first = i;
prev >= 0 ? (
a1 = 2*$pi*prev/t - $pi/2; a2 = 2*$pi*i/t - $pi/2;
gfx_set(0.5,0.6,0.9,0.7);
gfx_line(cx+cos(a1)*r,cy+sin(a1)*r, cx+cos(a2)*r,cy+sin(a2)*r, 1);
);
prev = i; last = i;
);
i += 1;
);
first>=0 && last>=0 && first!=last ? (
a1 = 2*$pi*last/t - $pi/2; a2 = 2*$pi*first/t - $pi/2;
gfx_set(0.5,0.6,0.9,0.7);
gfx_line(cx+cos(a1)*r,cy+sin(a1)*r, cx+cos(a2)*r,cy+sin(a2)*r, 1);
);
// Dots (colored by octave, blue→red) and MIDI note numbers
i = 0; loop(t,
ring_act[i] ? (
a = 2*$pi*i/t - $pi/2;
x = cx+cos(a)*r; y = cy+sin(a)*r;
oct = floor(ring_note[i] / t);
frac = min(1, oct / 10);
gfx_set(frac, 0.15, 1-frac, 1); gfx_circle(x,y,r*0.05,1);
gfx_set(1,1,1,0.5); gfx_circle(x,y,r*0.05,0);
gfx_x = x + r*0.06; gfx_y = y + r*0.03;
gfx_set(1,1,1,0.8);
gfx_drawnumber(ring_note[i], 0);
);
i += 1;
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment