SOFTWARE &
DOWNLOADS

SCRIPT COLLECTION FOR LOGIC PRO X (MIDI) SCRIPTER

I will hopefully add more scripts and downloadable content in the future. In the meantime, here is one script that may (hopefully) be of use to you. If you are new to the Logic Pro X Scripter plugin, there is lots of information available online. I will assume you have a basic knowledge of how to load it up and paste in custom scripts.

Dynamics & Expression Tracking Script
v1.0 27-Feb-2022

I didn’t want to purchase separate hardware (fader controller) to be able to move Dynamics & Expression in tandem when programming music with sample libraries. My keyboard already has a perfectly good mod wheel, and most of the time they move together or closely follow each other anyway. Instead, I created this script.

Once you paste in the code and run it, you will see a basic UI with user parameters. From there, you can limit the MIN & MAX range of each control, and also set the tracking mode to one of two possible options: Pick-Up or Proportional. In pick-up mode, the control moves whenever the mod wheel is in range but stops otherwise. In proportional mode, the control moves over the full range of the mod wheel but is scaled proportionally (linearly).

In proportional mode, if you invert the MIN and MAX values (setting a MIN higher than your MAX), that control will run backwards. This allows you to have Expression & Dynamics moving in opposite directions. In pick-up mode, if you invert the MIN and MAX values, that control will simply stick at the MAX value.

Finally, if you disable the "Follow CC1" checkbox, the CC11 Expression Control will no longer track with the mod wheel, but you will still be able to constrain your Expression values with your other parameter selections.

/*
Dynamics & Expression Tracking Script
v1.0 27-Feb-2022
CalicoLabs.net
*/

const ModID = 1;    	// You can edit this if you have remapped your controls
const ExpID = 11;    	// You can edit this if you have remapped your controls

function HandleMIDI(event)
{
	if (event instanceof ControlChange && event.number == ModID) {
	
		var ModMin = GetParameter("Mod Min");  	// minimum value for CC1 (Mod Wheel)
		var ModMax = GetParameter("Mod Max");		// maximum value for for CC1 (Mod Wheel)
		var ExpMin = GetParameter("Exp Min");   	// minimum value for Expression when following CC1
		var ExpMax = GetParameter("Exp Max");		// maximum value for Expression when following CC1
		
		//Calculate value for CC1 (dynamics) based on user settings
		ModValue = event.value;

		if (GetParameter("Mod Tracking") == 1) {
			// proportional mode: y=mx+b where m=(max-min)/127 and b=min
			event.value = Math.round(ModMin + (ModValue)*(ModMax - ModMin)/127);
		}
		else {	
			// else, use pick-up mode and only move when in range
			if (ModMax < ModMin) {
				ModMin = ModMax;  // prevent inversion of Min & Max values
			}
			if (ModValue < ModMin) {
				event.value = ModMin;
				}
			else if (ModValue > ModMax) {
				event.value = ModMax;
				}
		}
		event.send();  //send the CC1 event
	
		//Calculate value for CC11 (expression) based on user settings
		if (GetParameter("Follow CC1") == 1) {
			if (GetParameter("Exp Tracking") == 1) {
				// proportional mode: y=mx+b where m=(max-min)/127 and b=min
				event.value = Math.round(ExpMin + (ModValue)*(ExpMax - ExpMin)/127);
			}
			else {
				// else, use pick-up mode and only move when in range
				if (ExpMax < ExpMin) {
					ExpMin = ExpMax;  // prevent inversion of Min & Max values
				}
				if (ModValue < ExpMin) {
					event.value = ExpMin;
				}
				else if (ModValue > ExpMax) {
					event.value = ExpMax;
				}
				else {
					event.value = ModValue;
				}
			}
			event.number = ExpID;
			event.send();  //send the CC11 event along with the CC1 event
		}
	}
	
	else if (event instanceof ControlChange && event.number == ExpID && !(GetParameter("Follow CC1") == 1)) {
		// Handle case where Exp is not following CC1 but Exp range is limited per user selections
		var ExpMin = GetParameter("Exp Min");   	// minimum value for Expression when following CC1
		var ExpMax = GetParameter("Exp Max");		// maximum value for Expression when following CC1
		ModValue = event.value;
		if (GetParameter("Exp Tracking") == 1) {
			// proportional mode: y=mx+b where m=(max-min)/127 and b=min
			event.value = Math.round(ExpMin + (ModValue)*(ExpMax - ExpMin)/127);
		}
		else {
			// else, use pick-up mode and only move when in range
			if (ExpMax < ExpMin) {
				ExpMin = ExpMax;  // prevent inversion of Min & Max values
			}
			if (ModValue < ExpMin) {
				event.value = ExpMin;
			}
			else if (ModValue > ExpMax) {
				event.value = ExpMax;
			}
			else {
				event.value = ModValue;
			}
		}
		event.send();  //send the CC11 event
	}
	
	else if (GetParameter("Follow CC1") == 0 || ((GetParameter("Follow CC1") == 1) && !(event.number == ExpID))) {
		event.send();	//Pass through anything that is not CC1
						//but block CC11 if it is supposed to be tracking with CC1
	}
	// there is no final "else" block; if one of the three conditions is not met then no event is sent
}

// ResetParameterDefaults = true;

var PluginParameters = [{
    name: "~~~ CC1 (Dynamics) Controls ~~~",
    type: "text"
}, {
    name: "Mod Tracking",
    type: "menu",
    valueStrings: ["Pick-Up", "Prop"],
    defaultValue: 1,
    minValue: 0,
    maxValue: 1,
    numberOfSteps: 2
}, {
	name:"Mod Max", 
	defaultValue:127, 
	minValue:0, 
	maxValue:127,
	numberOfSteps:127,
	type:"lin"
}, {
	name:"Mod Min", 
	defaultValue:0, 
	minValue:0, 
	maxValue:127, 
	numberOfSteps:127,
	type:"lin"
}, {
    name: "~~~ CC11 (Expression) Controls ~~~",
    type: "text"
}, {
    name: "Follow CC1",
    type: "menu",
    valueStrings: ["Off", "On"],
    defaultValue: 1,
    minValue: 0,
    maxValue: 1,
    numberOfSteps: 2
}, {
    name: "Exp Tracking",
    type: "menu",
    valueStrings: ["Pick-Up", "Prop"],
    defaultValue: 1,
    minValue: 0,
    maxValue: 1,
    numberOfSteps: 2
}, {
	name:"Exp Max", 
	defaultValue:127, 
	minValue:0, 
	maxValue:127,
	numberOfSteps:127,
	type:"lin"
}, {
	name:"Exp Min", 
	defaultValue:30, 
	minValue:0, 
	maxValue:127, 
	numberOfSteps:127,
	type:"lin"
}, {
    name: "v1.0 (02/2022), CalicoLabs.net",
    type: "text"
}];