Morph between states?


I know you can morph between plugin presets, but I don’t believe there is there a way to morph between states, is there?

Often, my state changes consist of nothing more than plugin or rack gain changes. What I would like to be able to do in certain situations is have these gain changes be gradual instead of instantaneous. For example, have a pad sound fade in from 0 to full volume over 2 bars, or maybe 10 seconds. That way, I can press a pedal to trigger the state change, then have the pad fade in without me needing to take a hand off the keyboard.

I know there are some other ways to achieve this, but ideally, I would like it to be a state change so I can use the same trigger (my pedal) that I use for other state changes. Should I submit this as a feature request?


This has been on my extra-special wish list for a while but I’ve not suggested it as I have a feeling it’s a big undertaking for Brad.
And like you, Tom, the most useful thing is for it to handle gain changes.


As Tom said in his opening post, there are actually other ways of achieving this now.
It’s not, strictly speaking, a ‘morph’ - more of a crossfade.
A couple of midi files - one for up, one for down, tempo adjustable from Cantabile, could do an acceptable job, with bindings to the required destinations.


Thanks for the comments, Neil and Ade. The midi file approach was one thing I was consideriAre.

I know that in programming, nothing is ever as simple as you think it’s going to be, but given the fact that Brad already has code to morph from one plugin preset to another, I don’t think this would be terribly difficult. The only thing left is the variable mix parameters within Cantabile itself. This is mostly gain and pan.


Hey Tom,
Whether it’s integrated or achieved in the way I’ve suggested, there’s always parameters to tweak - that’s why I think you may be surprised at how simply this could be achieved with existing methodology.
But to be absolutely clear, what you’re after is a way of defining the transition time of a given rack or plugin from its current fader/pan setting to that of the next called state?


what you’re after is a way of defining the transition time of a given rack or plugin from its current fader/pan setting to that of the next called state?

You are correct.


I wondered if this could be achieved using a MIDI Plugin that tracks selected MIDI controller messages, and when they change, it emits controller messages to smoothly “glide” up/down to the new value, at some predefined rate. I thought something like that surely must already exist, and checked the Piz MIDI plugins etc., but so far I haven’t found one…


Check midiConverter3 in PizMIDI:

  • set its input to CC 1
  • leave input and output ranges as they are
  • set output to CC 1
  • set Param2 to Inertia: 100

Now midiConverter will smooth out any sudden parameter jumps. Unfortunately the “smoothing” time is still pretty short; no way to set it to, say, 2 seconds or so. It does help avoid nasty jumps in volume, though.

If I find some time to explore the VST SDK, I may take a stab at building a simple “smoother” plugin…




I’ve used media player to fade in (for example) string pads. Using Reaper, I created a MIDI file of control 7 moving from 0 to 90 over 2 measures. State change triggers the media player. This worked, but it was cumbersome. I abandoned it for a volume pedal. It would be nice to be built-in as a crossfade function.


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!



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


CC_MSG = 11;
positions = 1000;
values = 2000;
channels = 3000;
maxvalues = 1000;

// calculate samples per interval
interval = (srate*slider2/1000)|0;

stepsize = slider3;
ccsrc = slider1;

// initialize values
currval = 0;
targval = 0;
elapsed = 0;
increment = stepsize;

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;
            ) : ( // 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 



Wow, nice work, Torsten. I will definitely give this a shot. I didn’t even know about ReaJS.


Yeah, the whole ReaPlugs bundle is a bit of a hidden champion. I love the ReaDelay - add as many delay taps as you like! It’s lacking ping-pong-delay and modulation, though…

ReaJS is great for small MIDI transformations that go beyond Cantabile’s built-in features - anything that transforms notes or controllers is super-easy to build in JS, e.g. modifying note velocity based on a controller value is just a few lines in ReaJS.

The problem with controller smoothing is that it ReaJS works in audio buffer chunks, but controller smoothing needs to work across buffers and capture and send controller changes at the right time. That’s what made this script a bit convoluted and complicated.

Next on the list is a MIDI jump prevention script - Cantabile’s jump prevention only works with plugin parameters, but sometimes I need jump prevention on simple MIDI volume or modulations routes. That should be far easier in ReaJS :wink:




Torsten…What a guy!


I will 2nd that Adrian !!


Torsten - you’re the man!

I just tried this, and it works like a charm.

I sometimes have “dovetails” between sections, where it would be really nice for a chord in one instrument to fade gracefully as another starts playing, but where I don’t have a hand free to work a slider or a foot free to work my volume pedal. (Feet are often busy with footswitches for next song state, Leslie speed, etc, and the sustain pedal.) I was resigned to having these overlaps cruder than I’d like, but your script solves the problem.

Many thanks.