Jump to content

TIP: Scripter Keyswitches


Go to solution Solved by Dewdman42,

Recommended Posts

Articulation Keyswitching with Scripter

 

This post and thread will discuss some simple methods for sending articulation keyswitches using Scripter. The examples are being kept extremely simple in order to show the general concepts. More elaborate scenarios can also be handled. I will provide a few different approaches in different posts of this thread so you can explore them all and adapt to your own needs whatever works best. These are all small, simple and efficient scripts...any of which can overcome LogicPro's limitation of only 3 keyswitchs per articulationID in the articulationSet.

 

Sending Keyswitch per Aritculation ID.

 

Mainly the script needs to do the following in some manner:

 

  1. Look for articulationID in the incoming NoteOn object
  2. Based on the articulationID (if it exists), send one or more keyswitches
  3. Send the actual NoteOn

 

Here is a simple to understand example script:

 

https://gitlab.com/dewdman42/art2script/-/blob/main/keyswitcher.js

 

A couple points about the above simple example:

 

  1. Note that each Keyswitch needs both a NoteOn and a NoteOff event. The easy way to send a matching NoteOff is to simply use the NoteOn object and set the velocity to zero, which in LogicPro is considered the same as a NoteOff.
     
     
  2. Also, note that for these kinds of scripts to work, the articulationSet must not have any entries in the OUTPUT section because when there are entries in the OUTPUT section, the articulationID will be stripped from the event

 

Here is also a BASH script that can be used to generate a working Scripter Script by reading an articulationset file to get the names and keyswitches to use:

 

https://gitlab.com/dewdman42/art2script/-/blob/main/art2script.sh

 

 

Sending Keyswitches by Automation

 

Another interesting way to send keyswitches from Scripter is to use automation points instead of articulationID. This is sometimes preferable because you can see a graph with labels on an automation lane showing the articulations in effect.

 

artlane.thumb.jpg.4d20a8e5cb6f58a7bc98430466afbb88.jpg

 

Here is a simple example script doing something like that. This example is considerably more complicated then the above example, but it may get you started...

 

https://gitlab.com/dewdman42/art2script/-/blob/main/lanekeyswitcher.js

 

 

Any questions, comments or discussion will be welcomed here...

 

Please see later posts in this thread with more example scripts using different approaches just for the sake of discussion.

Edited by Dewdman42
  • Like 1
Link to comment
Share on other sites

Scripter can respond to midi and automation. You can setup smart controls to control the automation. Aside from that in order to switch via keyboard you would need to use Keyboard Maestro and have that send midi into LogicPro. If you use something like MetaGrid, then same thing..it will will need to send midi in response to you pressing an icon on the iPad. Either way, the midi has to be routed into the channel you are using to host Scripter...then you would be able to control Scripter that way, presuming you also update your scripts to listen for the incoming midi messages needed to change your CC#. Edited by Dewdman42
Link to comment
Share on other sites

and have it change "what" based on which cc values?

 

Not sure what you mean. I’m just trying to be able to switch from the articulation view and expression views in the midi automation piano roll view. I’ll still be using my mouse to switch the articulations

Link to comment
Share on other sites

  • 10 months later...

Here is an alternative script, just for sake of discussion. I still prefer the version in post#1, but this is here now for the sake of learning.

 

function HandleMIDI(event) {

   if( ! (event instanceof NoteOn) ) {
       event.send();
       return;
   }

   if(event.articulationID == undefined 
           || event.articulationID < 1) {
       event.send();
       return;
   }
   
   switch(event.articulationID) {
   case 1:
       Staccato(event);
       break;
   case 2:
       Legato(event);
       break;
   case 3:
       Spiccato(event);
       break;
   default:
       event.send();
   }
}

function Staccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#1", event);  
   
   event.send();
}

function Spiccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#2", event);  
   
   event.send();
}

function Legato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#3", event);  
   
   event.send();
}

var ks = new NoteOn;

function sendSwitch(ksName, ev) {
   ks.channel = ev.channel;
   ks.pitch = MIDI.noteNumber(ksName);
   ks.velocity = 100;
   ks.send();
   ks.velocity = 0;
   ks.send();
}
Edited by Dewdman42
Link to comment
Share on other sites

here is an example script that is able to also send CC switches...

 

function HandleMIDI(event) {

   if( ! (event instanceof NoteOn) ) {
       event.send();
       return;
   }

   if(event.articulationID == undefined 
           || event.articulationID < 1) {
       event.send();
       return;
   }
   
   switch(event.articulationID) {
   case 1:
       Staccato(event);
       break;
   case 2:
       Legato(event);
       break;
   case 3:
       Spiccato(event);
       break;
   default:
       event.send();
   }
}



function Staccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendCC(58, 13, event);
   sendSwitch("D#0", event);
   sendSwitch("D#1", event);  
   
   event.send();
}

function Spiccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#2", event);  
   
   event.send();
}

function Legato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#3", event);  
   
   event.send();
}

var ks = new NoteOn;

function sendSwitch(ksName, ev) {
   ks.channel = ev.channel;
   ks.pitch = MIDI.noteNumber(ksName);
   ks.velocity = 100;
   ks.send();
   ks.velocity = 0;
   ks.send();
}

var cc = new ControlChange;

function sendCC(num, val, event) {
   cc.number = num;
   cc.value = val;
   cc.channel = event.channel;
   cc.send();
}

 

That is accomplished by simply adding a sendCC function like this:

 

var cc = new ControlChange;

function sendCC(num, val, event) {
   cc.number = num;
   cc.value = val;
   cc.channel = event.channel;
   cc.send();
}
Link to comment
Share on other sites

here is an example Script where the keyswitches are only sent when the articulationID changes since the last note (on the same channel):

 


var lastArtID = new Array(17);
for (let chan=1; chan<=16; chan++) {
   lastArtID[chan] = 0;
}


function HandleMIDI(event) {

   if( ! (event instanceof NoteOn) ) {
       event.send();
       return;
   }

   if(event.articulationID == undefined 
           || event.articulationID < 1) {
       lastArtID[event.channel = 0];
       event.send();
       return;
   }
   
   
   if(event.articulationID == lastArtID[event.channel]) {
       event.send();
       return;
   }
   
   lastArtID[event.channel] = event.articulationID;
   
   
   switch(event.articulationID) {
   case 1:
       Staccato(event);
       break;
   case 2:
       Legato(event);
       break;
   case 3:
       Spiccato(event);
       break;
   default:
       event.send();
   }
}



function Staccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendCC(58, 13, event);
   sendSwitch("D#0", event);
   sendSwitch("D#1", event);  
   
   event.send();
}

function Spiccato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#2", event);  
   
   event.send();
}

function Legato(event) {
   sendSwitch("C-2", event);
   sendSwitch("D0", event);
   sendSwitch("D1", event);
   sendSwitch("C-1", event);
   sendSwitch("D#0", event);
   sendSwitch("D#3", event);  
   
   event.send();
}

var ks = new NoteOn;

function sendSwitch(ksName, ev) {
   ks.channel = ev.channel;
   ks.pitch = MIDI.noteNumber(ksName);
   ks.velocity = 100;
   ks.send();
   ks.velocity = 0;
   ks.send();
}

var cc = new ControlChange;

function sendCC(num, val, event) {
   cc.number = num;
   cc.value = val;
   cc.channel = event.channel;
   cc.send();
}

 

One other comment to make is that LogicPro articulation set behavior for handling notes without articulationID is to play the keyswitches found (if found at all) in the first item of the articulationSet list. This hidden behavior is often not desired, in fact I usually make sure to program my Articulation Set so that the first item on the list is a dummy entry with no keyswitches to make sure. In any case, whatever you desired behavior is, you may have to modify any of these example scripts accordingly to handle that specific situation exactly as you desire.

Link to comment
Share on other sites

  • 4 weeks later...

Hey Dewdman42 - I just want to really thank you for this. Not many replies on here, but this is SUPER useful and saved me. Basically I didn't think ahead before buying my first VSL instrument (Synchron Strings Pro) and then realised, "wait a minute, how can I build Articulation Sets for this when it has more than 3 required Keyswitches for some of the articulations?"

 

So this semi tutorial really saved me. I didn't/don't want to (at least not yet) go the X-DAW route. I've only just done some preliminary tests modifying your first example and it seems to be working for me.

 

I'll come back with any questions if I run into any problems and hopefully you can educate me further. But thanks again!

 

P.S. If I did want to do some reading to understand the code you've used in your first example better - as in to actually understand more of what's going on there, can you point me somewhere I can do that?

  • Like 1
Link to comment
Share on other sites

There is not much information out there about scripter other then what is in the logicpro effects manual (from the help menu) start there. Feel free to ask questions here or start a thread with general questions about scripter on the forum, some others May chime in too. Glad it’s helpful!
Link to comment
Share on other sites

Sending Keyswitches by Automation

 

Another interesting way to send keyswitches from Scripter is to use automation points instead of articulationID. This is sometimes preferable because you can see a graph with labels on an automation lane showing the articulations in effect.

 

artlane.jpg

 

Here is a simple example script doing something like that. This example is considerably more complicated then the above example, but it may get you started...

 

var articulation = [];

articulation[1] =  {word:"Articulation 1",  pitch:"C0"};
articulation[2] =  {word:"Articulation 2",  pitch:"C#0"};
articulation[3] =  {word:"Articulation 3",  pitch:"D0"};
articulation[4] =  {word:"Articulation 4",  pitch:"D#0"};
articulation[5] =  {word:"Articulation 5",  pitch:"E0"};
articulation[6] =  {word:"Articulation 6",  pitch:"F0"};
articulation[7] =  {word:"Articulation 7",  pitch:"F#0"};
articulation[8] =  {word:"Articulation 8",  pitch:"G0"};
articulation[9] =  {word:"Articulation 9",  pitch:"G#0"};
articulation[10] = {word:"Articulation 10", pitch:"A0"};

var VELOCITY=1;

/***************************************************************
***************** DO NOT EDIT BELOW HERE **********************
***************************************************************/


function HandleMIDI(event) {
   event.ksSend();    
}
Event.prototype.ksSend = function() {
   this.send();
};
NoteOn.prototype.ksSend = function() {
   SwitchMap.send(this);
};

var wordFlg=-1;

/*************************************************
* Setup SwitchMap object with pre-created
* events based on the user provided articulation 
* array
*************************************************/

var SwitchMap = {map:[]};

/*************************************************
* send function that will be called for all 
* NoteOn events to potenetially send keyswitches
***/
SwitchMap.send = function (event) {

   if( wordFlg >= 0 ) {
   
       var node = this.map[wordFlg];
   
       // If switches are defined, send them
       if(node != undefined) {
           for(var s=0;s<node.length;s++) {
               var e = node[s];
               e.channel = event.channel;
               e.port = event.port;
               e.send();
           }
       }
       
       wordFlg = -1;
   }
   
   // Send main note event
   event.send();
};

/***********************************************
* init function to load up the switches defined
* in articulation[] array, preallocate events
***/
SwitchMap.init = function() {

   this.map = [];
   for (var a = 1; a < articulation.length; a++) {
       var userArt = articulation[a];
       if (userArt != undefined) {        
           this.configure(a, userArt);
       }
   }
};

/**********************************************
* use this function to configure each 
* user provided entry
**/
SwitchMap.configure = function(artid, ksconfig) {
   
       // make sure wrapped by array
       if(this.map[artid] == undefined) this.map[artid] = [];
       
       var on = new NoteOn;
       if(typeof ksconfig.pitch == "string") {
           on.pitch = MIDI.noteNumber(ksconfig.pitch);
       }
       else {
           on.pitch = ksconfig.pitch;
       }
       
       if(ksconfig.velocity == undefined) {
           on.velocity = VELOCITY;
       }
       else {
           on.velocity = ksconfig.velocity;
       }
       this.map[artid].push(on);
       
       var off = new NoteOff;
       off.pitch = on.pitch;
       off.velocity = on.velocity;
       this.map[artid].push(off);    
};

/************************************************
* Callback called by LogicPro at the right time
***/
function Initialize() {
   SwitchMap.init();
}


function ParameterChanged(id, val) {
   if(id==0) {
       wordFlg=val;
   }
}

// build words array for automation
var words = ["Reset"];
for(var i=1;i<articulation.length;i++) {
   words.push(articulation[i].word);
}

var PluginParameters = [];
PluginParameters.push({
   type: "menu",
   name: "Your Articulation Lane",
   defaultValue: 0,
   hidden: true,
   disableAutomation: false,
   valueStrings: words
});

 

hi, i tried this and it works, but couldn't get multiple keyswitches per articulation to work, like for example in Synchron player when you have different dimesions so you need to send a C0 and C#1 to activate a cell. What would be the correct syntax to get that to work? i tried:

articulation[1] =  {word:"Articulation 1",  pitch:["C0","C#1"]}; 

but doesn't work

thanks

Link to comment
Share on other sites

Try this one, I made it more simple to understand as well, though technically speaking slightly less efficient but not enough to be worried about:

 

/**************************************************************
* Example Script that convert automation lane to keyswitches
*
* Note this example only works for a single midi channel,
* Otherwise seperate automation lanes will need to be defined
* for each midi channel in the articulation[] array
**************************************************************/

var articulation = [];

// Array of keyswiches per articulation

articulation[1] =  {word:"Articulation 1",  ks:["C0","C1"]};
articulation[2] =  {word:"Articulation 2",  ks:["C#0",[58,5]]};
articulation[3] =  {word:"Articulation 3",  ks:["D0"]};
articulation[4] =  {word:"Articulation 4",  ks:["D#0"]};
articulation[5] =  {word:"Articulation 5",  ks:["E0"]};
articulation[6] =  {word:"Articulation 6",  ks:["F0"]};
articulation[7] =  {word:"Articulation 7",  ks:["F#0"]};
articulation[8] =  {word:"Articulation 8",  ks:["G0"]};
articulation[9] =  {word:"Articulation 9",  ks:["G#0"]};
articulation[10] = {word:"Articulation 10", ks:["A0"]};

/***************************************************************
***************** DO NOT EDIT BELOW HERE **********************
***************************************************************/

var lastArtID = -1;
var currentArtID = -1;

function HandleMIDI(event) {

   // Everything other then NoteOn
   if( !(event instanceof NoteOn)) {
       event.send();
       return;
   }
     
   // If same artID as last note, skip keyswitching
   if(currentArtID == lastArtID
           || articulation[currentArtID] == undefined
           || articulation[currentArtID].ks == undefined) {
           
       event.send();
       return;
   }
   
   // remember new artID
   lastArtID = currentArtID;
   
   // send keyswitches
   let ksList = articulation[currentArtID].ks;
   
   for(let k=0; k<ksList.length; k++) {
   
       if(Array.isArray(ksList[k])) {
           sendCC(ksList[k], event);
       }
       else {
           sendSwitch(ksList[k], event);
       }
   }
    
   // send actual note    
   event.send();
}

// Function to send a single keyswitch

var ks = new NoteOn;
function sendSwitch(pitchName, evt) {
       ks.pitch = MIDI.noteNumber(pitchName);
       ks.channel = evt.channel;
       ks.velocity = 100;
       ks.send();
       ks.velocity = 0;
       ks.send();   
}

// Function to send a single CC switch

var cc = new ControlChange;
function sendCC(ccArray, evt) {
   cc.number = ccArray[0];
   cc.value = ccArray[1];
   cc.channel = evt.channel;
   cc.send();
}

function ParameterChanged(id, val) {
   if(id==0) {
       currentArtID=val;
   }
}


// build words array for automation
var words = ["Reset"];
for(var i=1;i<articulation.length;i++) {
   words.push(articulation[i].word);
}

var PluginParameters = [];
PluginParameters.push({
   type: "menu",
   name: "Your Articulation Lane",
   defaultValue: 0,
   hidden: true,
   disableAutomation: false,
   valueStrings: words
});
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...