I'm working on another in my MPE series of JSFX. This one goes along with the MPE pitchbend scaler I wrote about elsewhere in the forums.
This script is designed to rechannelize data from an MPE device, which will typically be sending on channels 2-15, and rechannelize it to channels 2-X, in situations where the receiving instrument is not MPE compatible, and/or cannot receive on so many channels. For instance, Kontakt requires you make a copy of your instrument for each channel you want to receive on. In this case, I would limit the number of channels to something less than 15 in many cases, as I only have 10 fingers anyway. But my 3 Seaboard Blocks are set up to send on channels 2-6,7-11 and 12-15 respectively, so if I want full range, I have to receive on all 15 channels. Similarly Spectrasonics gear only receives on channels 1-8, so that limits each block to 2 or 3 channels. (Roli says they are going to fix the issue of different blocks having to send on different channels, but I don't know when this may happen.)
You can adjust for this somewhat by going into the Roli Dashboard, and changing the number of channels you are sending on, but what if you want to layer 2 sounds with different polyphony settings? And constantly opening up the dashboard to make adjustments is annoying, even in cases where that would be sufficient.
Also, if you'd like to control an MPE instrument with a conventional controller or vice-versa, there are other hoops to jump through.
This plugin is designed to address these problems.
But I have 2 issues with it.
First, my algorithm for finding the oldest voice is not working at all, and I can't figure out why. For now I'm just modding by note count, which is less elegant, but works.
Second, I am getting guaranteed stuck notes when I hold down the maximum number of notes. (Max-1) simultaneous notes can be played all day, but add that one extra voice, and it sticks. It looks like the last note off is going to the wrong channel, but I can't see how that's happening.
Eventually, I hope this plugin will convert from MPE-Standard MIDI or vice-versa, but that functionality is not tested yet (it may work...) For now, I'm just trying to get MPE->MPE to work (with base channel set to 2 to use with typical MPE devices.)
I'd be most grateful for any help from the serious coders out there, thanks in advance!!! When it's done, I'll post it, and maybe someone will find it useful.
Code:
desc:MPE Rechannelizer
//tags: MIDI processing
//author: Eric Moon
slider1:s_mute=0<0,1,1{no,yes}>INPUT CONTROL: mute input
//slider3:s_midiport=0<0,16,1>midi port
//just set min, max and global channel the same for normal input?
slider5:s_ctrlrType=0<0,1,1{normal,mpe}>controller midi type
//chan out for normal vst, first chan out for mpe
slider6:s_baseChanOut=0<0,15,1{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>base channel out
slider7:s_mpePoly=1<1,15,1>mpe channel count out
slider10:s_vstType=0<0,1,1{normal,mpe}>VST midi type
slider11:s_ATtoCC=0<0,12,1{off,1,2,3,4,5,6,7,8,9,10,11}>Convert AT to CC
//slider16:s_loNote=0<0,127,1>low note
//slider17:s_hiNote=127<0,127,1>high note
//slider35:s_glideToNotes=0<0,1,1{no,yes}>mpe glides to notes
// these lines tell Reaper the effect has no audio input/output,
// which enables processing optimizations.
// MIDI-only FX should always have these lines.
in_pin:none
out_pin:none
options:no_meter
/********************************* INIT - midi methods ************************/
@init
noteOff = $x80;
noteOn = $x90;
CC = $xB0;
PChg = $xC0;
ATouch = $xD0;
PBend = $xE0;
roliCC = 74;
mod = 1;
function getStatus (msg) ( msg & $xF0; );
function getChannel (msg) ( msg & $x0F; );
function isMpeControl(status,msg2,msg3) (
(((status == CC) && (msg2 == roliCC))
|| ((status == CC) && (msg2 == mod))
|| (status == ATouch)
|| (status == PBend)) ? 1 : 0;
);
/************************************** chan array ******************************/
order = 0;
voiceCount = 0;
chInput = 0;
ch_size = 3; // 2 entries per note: channel, order,note
//buflen = r_size * 16 midi chans;
function clearArray() local(i) (
i = 0;
loop
(
ch_size * 16,
chInput[i] = -1;
i = i + 1;
)
);
function storeChan(chan,voice,order,note) (
chInput[ch_size * chan] = voice;
chInput[ch_size * chan + 1] = order;
chInput[ch_size * chan + 2] = note;
);
function ch_voice(chan)( chInput[ch_size * chan ]; );
function ch_order(chan)( chInput[ch_size * chan + 1]; );
function ch_note(chan) ( chInput[ch_size * chan + 2]; );
function voiceForNote(note) (
i = 0;
while
( ch_note(i) != note && i < s_mpePoly ?
i = i + 1;
);
ch_voice(i);
);
function getVoice(chan,note) local(voice) (
s_vstType == 0 ? voice = s_baseChanOut :
s_ctrlrType = 1 && s_vstType == 1 ? voice = ch_voice(chan) + s_baseChanOut :
//if the source channel was constant, can't sort by channel in....
s_ctrlrType = 0 && s_vstType == 1 ? voice = voiceForNote(note) + s_baseChanOut :
voice;
);
//should look for the first note we played to steal
function getOldestVoice() local(first,age) (
/* i = 0;
first = 1000000; //unlikely
loop
//for each mpe midi channel do
( s_mpePoly,
//check for lowest note order value....
age = ch_order(i);
age < first ? first = age;
i = i + 1;
);
i = 0;
while (
ch_order(i) != first;
i = i + 1;
);
i;*/
order % s_mpePoly; //hack for testing, simplest voice allocation
);
clearArray();
@slider
@block
//if all keys are up, reset the voice count, so it doesn't get huge.
voiceCount == 0 ?
( order = 0;
clearArray;
);
while
(
midirecv(offs, msg1, msg2, msg3) ?
(
status = getStatus(msg1);
channel = getChannel(msg1);
status == noteOn && msg3 > 0 ? // note-on
(
note = msg2;
//(key <= s_hiNote) && (key >= s_loNote) && (!s_mute) ?
(
vel = msg3; // velocity
oldest = getOldestVoice();
//storeChan to rechan pb and mod from Roli
storeChan(channel, oldest, order, note);
voice = getVoice(channel,note);//ignores oldest chan in normal mode
midisend(offs,status + voice, note, vel);
order = order + 1;
voiceCount = voiceCount + 1;
)
)
:
(status == noteOn && msg3 = 0 ) || status == noteOff ? // note-off, convert pitch
(
note = msg2;
vel = msg3;
voice = getVoice(channel,note);
midisend(offs,status + voice,note,vel);
voiceCount = voiceCount - 1;
)
: //mpe controls
(isMpeControl(status,msg2,msg3)) ?
( // what do we do in normal->mpe mode? shouldn't have any of this data....
voice = getVoice(channel,note); //rechannelize!
(status == ATouch) && (s_ATtoCC > 0) ?
(
status = CC;
msg3 = msg2;
msg2 = s_ATtoCC;
);
midisend(offs,status + voice,msg2, msg3);
);
);
);