I finally got around to building a script for ReaJS to do exactly this - it allows you to select a controller, then tracks it and smoothes it out. It has three parameters:
- Controller - this is the CC number to be smoothed
- ms per step - milliseconds between each cc increment / decrement
- step increment - how much will the current value be incremented / decremented per step
It will smooth all CC values that come in on MIDI channel 1; send CC values on MIDI channel 2 to send “hard” CCs (no smooting). All output will be redirected to MIDI channel 1.
When loaded, the plugin will initialize the current controller value to zero, so to avoid any nasty jumps when using for the first time, you should initialize it by sending your starting value before playing anything (Song->OnLoad or SongState->OnLoad)
I use it to smoothly fade some rack volumes from my current position (wherever I have currently set it via a MIDI fader) to a fixed state at the next song state: I have one binding from my controller to the CC Smoother rack and another from SongState->OnLoad. So wherever I have moved the volume level in the previous song state, it will move smoothly to the pre-defined initial value for the next song state.
The script is a quick, sloppy one I just created in a couple of hours, but it should do the trick. Simply create a new MIDI effect file in ReaJS, then paste the script below there and give it a try. You should be able to create all kinds of crazy crossfades with it…
Have fun!
Cheers,
Torsten
desc: MIDI CC Smoother
//tags: MIDI processing
slider1:1<0,127,1{0 Bank Sel M,1 Mod Wheel M,2 Breath M,3,4 Foot P M,5 Porta M,6 Data Entry M,7 Vol M,8 Balance M,9,10 Pan M,11 Expression M,12 Ctrl 1 M,13 Ctrl 2 M,14,15,16 GP Slider 1,17 GP Slider 2,18 GP Slider 3,19 GP Slider 4,20,21,22,23,24,25,26,27,28,29,30,31,32 Bank Sel L,33 Mod Wheel L,34 Breath L,35,36 Foot P L,37 Porta L,38 Data Entry L,39 Vol L,40 Balance L,41,42 Pan L,43 Expression L,44 Ctrl 1 L,45 Ctrl 2 L,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64 Hold P sw,65 Porta sw,66 Sustenuto sw,67 Soft P sw,68 Legato P sw,69 Hold 2 P sw,70 S.Variation,71 S.Timbre,72 S.Release,73 S.Attack,74 S.Brightness,75 S.Ctrl 6,76 S.Ctrl 7,77 S.Ctrl 8,78 S.Ctrl 9,79 S.Ctrl 10,80 GP B.1 sw,81 GP B.2 sw,82 GP B.3 sw,83 GP B.4 sw,84,85,86,87,88,89,90,91 Effects Lv,92 Trem Lv,93 Chorus Lv,94 Celeste Lv,95 Phaser Lv,96 Data B. Inc,97 Data B. Dec,98 NRP L,99 NRP M,100 RP L,101 RP M,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127}>Controller
slider2:0<0,100,1>ms per Step
slider3:1<1,5,1>Step increment
@init
CC_MSG = 11;
positions = 1000;
values = 2000;
channels = 3000;
maxvalues = 1000;
@slider
// calculate samples per interval
interval = (srate*slider2/1000)|0;
stepsize = slider3;
ccsrc = slider1;
// initialize values
currval = 0;
targval = 0;
elapsed = 0;
increment = stepsize;
@block
arraypos = 0;
(interval == 0) ? ( // no smoothing at all
// simply forward all input
while (
midirecv(mpos, msg1, msg23) ? (
midisend(mpos, msg1, msg23);
);
);
) : ( //else, we really need to smooth
while (
midirecv(mpos, msg1, msg23) ? (
status = msg1;
statusHi = (msg1/16)|0;
statusLo = msg1-(statusHi*16);
msg3 = (msg23/256)|0;
msg2 = msg23-(msg3*256);
(statusHi == CC_MSG && msg2 == ccsrc) ? ( // we have a controller to smooth
// now collect it into the arrays
positions[arraypos] = mpos;
values[arraypos] = msg3;
channels[arraypos] = statusLo;
arraypos+=1;
) : ( // not my controller - simply pass thru
midisend(mpos, msg1, msg23);
);
); // end if received
); // end while
// now we create the smoothing data, looping through the sample buffer
currsamp = 0;
currcont = 0; //pointer into controller array
while (currsamp < samplesblock) (
while (currcont < arraypos && currsamp == positions[currcont]) ( // we have a valid controller at the current sample position
(channels[currcont]==1)? ( // hard control
targval = values[currcont];
currval = targval;
elapsed = 0;
midisend(currsamp,176,ccsrc, currval);
) : ( // not a hard control
targval = values[currcont];
(targval >= currval)? increment = stepsize : increment = -stepsize;
);
currcont += 1;
); // loop through valid controllers at current position
// now compare currval with target and step towards it if necessary and right time
(currval != targval && elapsed >= interval) ? ( // we need to send a new value now
newval = currval + increment;
// check for overshoots here
(increment < 0) ? ( // stepping down
newval < targval ? newval = targval;
) : ( //stepping up
newval > targval ? newval = targval;
);
currval = newval;
// now send new value and reset elapsed
midisend(currsamp,176,ccsrc, currval);
elapsed = 0;
);
currsamp += 1;
elapsed += 1;
); // loop thru samples
);