Jump to content

Slew/Glide for Aftertouch/Midi CC


eserpa

Recommended Posts

Hey everyone, this site has been a fantastic source of knowledge through the years, yet this is my first post ever.

 

I been toying around with a Roli controller and I’ve encountered that the aftertouch (and many other keyboards) tends to be way too abrupt and inexpressive.

 

Using Alchemy’s envelope follower or Kontakt’s lag has helped a lot but not all plugins are as easy to workaround, hence this post.

 

I’m wondering if there is a way to script a midi message like aftertouch to midi cc with lag/smooth/glide (attach and decay could be useful too) functions to smooth out the abrupt changes. Minimum and maximum values would be great too. I know a little bit of code but this has been way out of my league.

 

Polyphonic aftertouch would be great also but probably a nightmare to script.

 

This slew/glide script could also help with jumpy cc midi cc data and make it smoother.

 

Looking forward to all your comments and suggestions, thanks in advance!

Edited by eserpa
Link to comment
Share on other sites

Here's one concept:

- receive an AT event

- if it's the first AT event, store it in "currentAT"

- also store it in "lastAT"

- pause for "lagMS"

- if lastAT > currentAT, increase currentAT by 1

- if lastAT < currentAT, decrease currentAT by 1

- send currentAT

- if another AT event has come in in the meantime, store it in lastAT

- if currentAT ≠ lastAT, jump to the pause for lagMS line

 

lagMS is your ramp speed control, you can set up a GUI fader for this. Start with a center value of, say, 5ms, this will spread 127 steps over 640ms.

 

I'm sure there's more elegant ways to code this, but this can get you started.

Link to comment
Share on other sites

I’m wondering if there is a way to script a midi message like aftertouch to midi cc with lag/smooth/glide functions to smooth out the abrupt changes. Minimum and maximum values would be great too. I know a little bit of code but this has been way out of my league.

 

Please describe what you mean exactly by "lag/smooth/glide".

 

Polyphonic aftertouch would be great also but probably a nightmare to script.

 

its not really that hard

Link to comment
Share on other sites

so one quick fix for now...might be to use this site and adapt it:

 

https://sumire-io.gitlab.io/midi-velocity-curve-generator/

 

gen.thumb.jpg.c2e665cb38f74293251b6c60f9e2d5fc.jpg

 

You can basically drag the curve to a velocity curve you like and then copy and paste the javascript into Scripter. But in order to work with aftertouch, you'd need to modify the code slightly.. Modify just the HandleMIDI function more like this:

 

function HandleMIDI(event) {
 if(event instanceof ChannelPressure) {
   event.value = velocities[event.value];
 }
 event.send();
}

 

Play around with different aftertouch scaling curves until maybe it responds the way you want it too.

 

If you want to elaborate more on what you had in mind for glide, etc.. I can comment more...

Link to comment
Share on other sites

I’m wondering if there is a way to script a midi message like aftertouch to midi cc with lag/smooth/glide functions to smooth out the abrupt changes. Minimum and maximum values would be great too. I know a little bit of code but this has been way out of my league.

 

Please describe what you mean exactly by "lag/smooth/glide".

 

Polyphonic aftertouch would be great also but probably a nightmare to script.

 

its not really that hard

 

What I mean is that these changes in pressure are far to abrupt to be musical in most situations in my opinion. It is a great to have if you want to do a fast swell or transition fx but for evolving and smooth filter cutoffs it tends to be too fast.

Link to comment
Share on other sites

so one quick fix for now...might be to use this site and adapt it:

 

https://sumire-io.gitlab.io/midi-velocity-curve-generator/

 

gen.jpg

 

You can basically drag the curve to a velocity curve you like and then copy and paste the javascript into Scripter. But in order to work with aftertouch, you'd need to modify the code slightly.. Modify just the HandleMIDI function more like this:

 

function HandleMIDI(event) {
 if(event instanceof ChannelPressure) {
   event.value = velocities[event.value];
 }
 event.send();
}

 

Play around with different aftertouch scaling curves until maybe it responds the way you want it too.

 

If you want to elaborate more on what you had in mind for glide, etc.. I can comment more...

 

What an interesting solution. Thanks again for being super helpful. Will test all these options and report back!

Link to comment
Share on other sites

Agreed, aftertouch is often way too touchy to be usable.

I too would like a way to slow down (slew) aftertouch messages...

Preferably with separate Attack and Release time controls.

 

Yes! I think this would be great too. Logic's Alchemy has Attack Release and Scale, personally scale is usually at 100% but over scaling could interesting too, a great way yo make it smooth but have a shorter span of pressure.

 

Thanks to everyone, I don't know why I didn't joined this community sooner, what a great forum!

Link to comment
Share on other sites

Here's one concept:

- receive an AT event

- if it's the first AT event, store it in "currentAT"

- also store it in "lastAT"

- pause for "lagMS"

- if lastAT > currentAT, increase currentAT by 1

- if lastAT < currentAT, decrease currentAT by 1

- send currentAT

- if another AT event has come in in the meantime, store it in lastAT

- if currentAT ≠ lastAT, jump to the pause for lagMS line

 

lagMS is your ramp speed control, you can set up a GUI fader for this. Start with a center value of, say, 5ms, this will spread 127 steps over 640ms.

 

I'm sure there's more elegant ways to code this, but this can get you started.

 

Gosh I wish I knew more Java. But these variables sound like they would do the trick.

Link to comment
Share on other sites

  • 3 months later...

Quick question

 

How would you:

 

- pause for "lagMS"

 

I figure you could sendAfterMilliseconds, but there doesn't seem to be any wait or timing functions while inside a script, only delaying sending the midi data unless I'm missing something?

 

Thanks!

Link to comment
Share on other sites

you can use Javascripter timing functions to delay based on the real time clock sure. That is always possible, but keep in mind that Scripter operates on the midi AHEAD of schedule. Its operating on the midi before you actually hear it...so... realtime functions like that may not be that precise. But you can definitely use them.

 

if you want to delay things synchronized to the music you hear, then you have to use functions that inspect the current beat position and make calculations as you go to figure out if you have passed the lag time you seek to wait for, then do whatever you want...based on the beat position which now matches however many MS you wanted to lag for.

Link to comment
Share on other sites

yes you can send from ProcessMIDI(). The only difference is that HandleMIDI gets called once per incoming event. ProcessMIDI gets called over and over again once per process-block...without receiving any midi event. So you use HandleMIDI always to process incoming midi, and you can use it to send out midi. PRocessMIDI you use just to do whatever you want to do, including send midi, but it doesn't know about incoming midi, that is handled in HandleMIDI.

 

Use global variables to share data between the two functions and over hte course of multiple calls to them.

Link to comment
Share on other sites

so a few more words about timers and such in Scripter...

 

First, generally any of the javascript functions you normally might use in say a browser to set a "timer" that triggers a function call later at a predetermined amount of time.... these won't work in Scripter. Scripter operates in a very short period of time called a "process block". This is how all AU plugins work actually. Scripter is no different. When you have a chain of plugins, they each take turns doing their work on the current process-block..which will be some short period of time, likely closely resembling your buffer size.

 

Scipter is the same way. ProcessMIDI is called once for every process-block of time. During this short period of time you can call many javascript functions, but it has to execute very short and quickly...in much less time then the actual process-block...since other plugins also need time to do their work for that process-block, etc. So the idea is ProcessMIDI does some work, hopefully nothing taking too long, and then control is passed back to LogicPro...and there is no javascript context to fire off a timer event..

 

So what CAN you do...

 

you can use event.sendAfterMilliseconds or event.sendAfterBeats for one thing. For the actual sending of events, that works good for scheduling them in the future.

 

But what if you need to execute some Javascript code at some point in the future? Well that is the question then.

 

what you can do is use the Date.now() function to look at the current timestamp (in ms since 1970). Every time ProcessMIDI is called you look at Date.now() and you will know when so many ms have gone by, when enough have gone by, then go ahead and fire off your javascript.

 

That will work, but it's imprecise. For one thing, that will fire off javascript code to happen at the time the code is actually executed, which will always be ahead of the actual real time music. Javascript doesn't run in real time like you hear the metronome going by. LogicPro is always trying to run all kinds of calculations ahead fo the music. All plugins process their part of the process-block ahead of time and fill the buffer and finally when the buffer is flushed to your sound card later, then the results are heard. But the exact timing of when each plugin will have its turn on the process block, ahead of time...is not knowable. You will never know exactly when this code is executing in non-realtime ahead of the music. So this will definitely work...but its not completely precise. Depending on how accurate you need to be, this may work totally fine.

 

If you absolutely want sample accurate timing of things to happen, then you have to get used to working with the beatPosition every time ProcessMIDI is called. In that way you always run whatever javascript code you need to do, but the main thing is, if you plan to send out some midi, you schedule it on the proper beatPosition (which is a sample accurate fractional value) and the results will be exactly in the timing you want.

 

One downside is that beatPosition is not an exact amount of real time. 1.5 beats represents a completely different mount of time at one tempo compared to 1.5 beats at a different tempo. So how can you know when so many ms have gone by? You'd literally have to calculate it each process-block, look at the tempo for that process-block and use the beatPosition delta and guess at how many ms have gone by. But this is a little imprecise too.

 

well anyway, It depends on what you're wanting to do, but generally I would recommend you work with beatPosition if you are planning to output midi using correct timing. But there you have some options to play with.

Link to comment
Share on other sites

Thanks!

 

Just trying to wrap my head around exactly how to lag midi data via a script (like applying a lag amount in Kontakt modulators) like mentioned above. Would be super helpful for a project I'm working on.

 

I know that the AT info can be sent delayed, but then it's not able to be used by the script anymore, like if new AT data came in that might conflict with it.

 

Was thinking of something like, when you release AT - start sending the AT value slowly down to zero - then if new AT info comes in - interrupt that descent and start sending the AT value up to the new info.

 

All the experiments I've been playing around with in ProcessMIDI (thanks again for that!) have worked, but since the calculations all happen instantly there's no time to check for changed conditions.

 

Is there a way to wait inside of a loop, and break the loop under conditions? Or do you check every x milliseconds (some way other than setTimeout)?

 

Thanks again!!!

Link to comment
Share on other sites

no you have to bear in mind that each time process-block of time goes by.... two things will happen, where LogicPro will call callback functions that you provide in Scripter.

 

  1. ProcessMIDI will be called once
  2. if there are any incoming midi events in that process-block, then each one will cause HandleMIDI to be called on its behalf.

 

So anyway, what I hear you saying is that you can't use sendAfterMilliseconds to send a trail of AT events, because if more AT events come in from the midi controller you want that to supersede the stuff you may have tried to schedule. And once you schedule it with sendAfterMilliseconds, you can't take it back.

 

But anyway what you can do is create global variables to keep track of your AT value. When HandleMIDI is called with an AT event, you set the global variable to that value, and probably send the event right now. and at that time maybe you set some other variables for whatever future trail of automatic AT events you are hoping to send.

 

then each time ProcessMIDI is called, you look at the current beat position and you look at those global variables and you send out just the AT events for that brief little period of time. the next call to ProcessMIDi will send the next slice of the AT trail, etc..

 

then if HandleMIDI is called again with another AT, you overwrite those global variables to change the AT curve to where it needs to be so that the following calls to ProcessMIDi will be on a new curve with a new trail of AT to send out.

Link to comment
Share on other sites

here's a very simple brief example

 

var NeedsTimingInfo = true;

var currentLevel = 0;
var lastBeat = 0;
var GAP = 0.00625;   // approx 12.5ms at 120bpm

function HandleMIDI(event) {

   // Check for AT event
   if(event instanceof ChannelPressure) {
       let info = GetTimingInfo();
       currentLevel = event.value;
       lastBeat = event.beatPos;
   }
   
   event.send();
}

var at = new ChannelPressure;
at.channel = 1;

function ProcessMIDI() {

   let info = GetTimingInfo();
   
   if(info.blockStartBeat > lastBeat + GAP) {
      
       if(currentLevel > 0) {
           currentLevel--
           at.value = currentLevel;
           at.send();
           lastBeat = info.blockStartBeat;
       } 
   }
}

 

the above only works for midi channel 1. It will have to be more complicated if you want to track more then one midi channel at the same time. But anyway, the list is...when you play some AT from your keyboard...it will be sent thru and then each process-block later we check to see if approx 12.5ms has gone by (at 120BPM) and each time it has, then send another AT event that is 1 value lower then the previous, etc..and do that every 12.5ms until it reaches zero or unless another AT is received then jump to that value and keep going.

 

Its just a simple example so you can see how to catch time passing by and send out stuff. It can get more complicated if you are trying to be more precise or handle multiple midi channels among other things, but that should get you started.

Link to comment
Share on other sites

Thanks so much!!

 

The problem I've found with AT is that it actually doesn't throw enough data. Like a push can jump from 0-30-70, so scaling it can only do so much as far as smoothing it out. What I've found is needed is multiplying the info (like making a loop to create the data from 0-30) but that data needs to be spread out in time, which would really need some kind of wait before executing ability. Which is a bummer that the scripter doesn't really have (as far as I understand).

 

Its for a project I'm working on so I'll keep poking away at it. Lot's of things I've tried have gotten me almost there. Hopefully it'll all come together in something useable : )

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...