Jump to content

MIDI Velocity Curve Generator


keni

Recommended Posts

Hello!

 

I created a tool that lets you create custom MIDI velocity curves that you can load in Scripter. The tool is browser-based, and is available here:

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

 

You can move the points to generate a velocity curve that suits your playing style (double click to add or remove points). The Scripter JavaScript code is generated in real-time in the editor below. The first line of the code (comment) is the curve data in JSON format. You can paste this JSON string into the input field to load an existing velocity curve in the curve editor.

 

I created this tool because my Yamaha keyboard has a very weird velocity curve (maximum velocity is about 110). This has caused problems with sampled piano libraries, because I cannot reach any velocities over 110 when I'm playing. I'm aware of the Velocity Processor MIDI Plugin in Logic, but it can only do very basic velocity manipulation in my opinion.

  • Like 1
Link to comment
Share on other sites

keni, VERY cool! I'd like to hear more about how you setup the webpage on gitlab. Clever way to provide a UI, I like it! It makes me think I'd like to use a similar approach for some other Scripter things i have that badly need a better UI.

 

Thank you very much. The webpage is a git repository on Gitlab. The source code repository is available here: https://gitlab.com/sumire-io/midi-velocity-curve-generator.

 

You can use Gitlab Pages to host static HTML pages on Gitlab. Here is a step-by-step tutorial: https://about.gitlab.com/2016/04/07/gitlab-pages-setup/.

If your repository consists only of a static HTML site, like in my case, it's very simple. You just need to create one file in the root folder, as instructed in the tutorial (Option A section). Github has a similar Github Pages feature as well, if you prefer Github. https://help.github.com/en/articles/what-is-github-pages

Link to comment
Share on other sites

  • 1 year later...
I'm not very versed in MIDI, so good chance that I don't get it... but the LFO controlling the volume is something different from just applying a fixed factor to a velocity value based on a table/function/factor.

The Modulator doesn't just do a curve, but the patterns can be anything.

Here's just a few but it's up to. you to create ANY type of pattern as well as following a RATE.

6.thumb.png.da4ce894d2b891105fe50a477f80d7a2.png5.thumb.png.f56745873fa121cce745c044f25ffbb4.png4.thumb.png.a152d8a530c075937fb8993f0896e7f8.png3.thumb.png.055b9492d1ecbec94b0b7dca4f99a9b4.png2.thumb.png.3e2c90418a127b34341c8615d486922a.png1.thumb.png.136852e95f0d9a8069f519cefcf25185.png

 

The other thing I'll mention as well is, put this on a External Instrument track using IAC and you'll be able to capture the pattern and then you can further modify this as well.

Link to comment
Share on other sites

The environment is the nest place to do this with a transformer map, so that the velocity adjusted midi will be recorded to track regions.

 

Otherwise the above website generates a script you can tweak slightly to use in scripter for velocity. If those don’t work I have a velocity curve script I shared in the midifx sub forum a few years ago that should work for you.

Link to comment
Share on other sites

Using the above works fine.

Still I would love to find a MIDI fx plugin with similar functionality to the Velocity Editor in Pianoteq.

What's nice about that: curve can have any shape, visual feedback while playing/creating curves, easier to use than environment transformer.

In the meantime I can get by with the script fed by the tool above.

Ultimately I'll have to get a better masterkeyboard at some point ;-)

Link to comment
Share on other sites

I have looked many times and unfortunately there isn’t a dedicated velocity curve midifx plugin for Mac out there.

I was questioning my google-skills because of this ;-)

Seems to me this is such a frequent use-case... curious that such a plugin does not exist

Maybe a business opportunity for some smart people here on the forum ;-)

Link to comment
Share on other sites

It would not be a hard one to do in JUCE maybe I will eventually.

 

here is a script I made a few years ago that might help you since its a little more interactive, you can slide a slider to dial in an exponential curve that works, you just can't really see what the curve looks like... Well I think there is a way to have it display some kind of textual curve on the Scripter window if you hit a button or something, I can't remember now, but it works, last time I checked which was a few years ago.

 

NOTE - looks like I didn't include velocity in this version, but I can add that later, I am running out the door right now, or you can, it would not be a hard add.

 

//
// EventGamma
// Version 0.4
//
// This script will provide a non-linear accleration to CC, Pitchbend and 
// aftertouch midi events.  The factor value establishes the amount of curve
// above or below the linear line.  A factor of zero will be the linear line
// 
// TODO, generate a lookup table to avoid using math during PLAY

var NeedsTimingInfo = true;
var PluginParameters = [];

function HandleMIDI(event) {

   var gamma = GuiParameter(0);
   
   // if the gamma is 1 then just forward the event.
   if (gamma==1) {
       event.send();
       return;
   }
   
   // if CC events are being included, then range is 0-127
   if (event instanceof ControlChange && GuiParameter(3) == 1) {
       event.value = scale(event.value, 127, gamma);
   }
   
   // if Pitch Bend events, the range is -8291 to 8192
   else if (event instanceof PitchBend && GuiParameter(4) == 1) {
       
       // if event.value is not zero, then accelerate it.  use offset to 
       // force positive range 
   	    if (event.value!=0) {
   	        const OFFSET = 8192;
           var temp = event.value+OFFSET;
           var maxPitch = OFFSET+OFFSET;
           event.value = scale(event.value, maxPitch, gamma)-OFFSET;
       }
   }
   
   // if aftertouch, then range is 0-127
   else if (event instanceof ChannelPressure && GuiParameter(5) == 1) {
       event.value = scale(event.value, 127, gamma);
   }
   else if (event instanceof PolyPressure && GuiParameter(6) == 1) {
       event.value = scale(event.value, 127, gamma);
   }
   
   // send event
   event.send();
}

//============================================
// couple of globals related to the graphing
//============================================

var gGraphFlush = false;
var gGraph = [];
const MAXFLUSH = 10;

//======================================================
// Scales the value based on gamma and range
//
function scale(value, range, gamma) {

   // off means off, no matter what.  Should we do this?
   if(value == 0) {
       return 0;
   }
   
   // calculate scaled value
   var out = Math.round(range * Math.pow((value/range),gamma)); 
   // for incoming value over 1, always scale to at least 1
   if(out == 0) {
       out = 1;
   }

   return out;
}

//===================================================================
// function that will send to console a graph if the selected curve
//
function plotCurve(gamma) {
   
   // if flush in progress then ignore this
   if (gGraphFlush) {
        return;
   }
   
   var graph = new Array(64);
   
   for (var x=0;x<64;x++) {
       graph[x] = new Array(32);
       for (var y=0;y<32;y++) {        
           graph[x][y] = 0;
       }
   }
       
   // calculate Y values, cut X size in half and cut Y value in half again to shrink height of graph
   for (var x=0;x<64;x++) {
       var y = scale(x*2, 127, gamma)/4;
       graph[x][Math.round(y)] = 1;
   }
   
   // build output strings, 
    for(var y=31;y>=0;y--) {
       var str = "|";
       for (var x=0;x<64;x++) {

           if (graph[x][y] == 1) {
               str = str + "x";
           }
           else if (x/2==y) {
               str = str + ".";
           }
           else {
               str = str + " ";
           }
       }
       gGraph.push(str);
   }

   // add x axis
   var xaxis = "+";
   for(var x=0;x<64;x++) {
       xaxis = xaxis + "-";
   }
   gGraph.push(xaxis);
   
   gGraphFlush = true;
}

//=============================================
//  handle when they hit the show graph button
//
function ParameterChanged(idx, val) {

   PluginParameters[idx].data = val;
   
   if (idx == 1) {
       plotCurve(GuiParameter(0));
   }
}


//=========================================================
// use Idle() function to flush graph to console
//
function Idle() {

   if(gGraphFlush) {
       
       // Trace("printing graph");
       
       var i = 0;
       while( gGraph.length > 0 && i < MAXFLUSH) {
           Trace(gGraph.shift());
           i++
       }
       if (gGraph.length <= 0) {
           gGraphFlush = false;
       }
   }
}

PluginParameters.push({
   name: "Gamma",
   type: "log",
   defaultValue: 1,
   minValue: .1,
   maxValue: 7.9,
   numberOfSteps: 100,
   hidden: false,
   disableAutomation: false
});

PluginParameters.push({
   name: "Show Graph",
   type: "momentary",
   hidden: false,
   disableAutomation: true
});

PluginParameters.push({
   name: "-------- Filter -------",
   type: "text",
   hidden: false,
   disableAutomation: true
});

PluginParameters.push({
   name: "Control Change",
   type: "checkbox",
   defaultValue: 1,
   hidden: false,
   disableAutomation: true
});

PluginParameters.push({
   name: "Pitch Bend",
   type: "checkbox",
   defaultValue: 0,
   hidden: false,
   disableAutomation: true
});

PluginParameters.push({
   name: "Channel Pressure",
   type: "checkbox",
   defaultValue: 0,
   hidden: false,
   disableAutomation: true
});

PluginParameters.push({
   name: "Poly Pressure",
   type: "checkbox",
   defaultValue: 0,
   hidden: false,
   disableAutomation: true
});

function GuiParameter(id) {
   // if script was recently initialized, reload GUI value
   if(PluginParameters[id].data == undefined) {
       PluginParameters[id].data = GetParameter(id);
   }
   if(id < PluginParameters.length) {
       return PluginParameters[id].data;
   }
}

function Reset() {
}

Reset();
Edited by Dewdman42
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...