Skip to content

Instantly share code, notes, and snippets.

@TomAuger
Created November 19, 2025 19:59
Show Gist options
  • Select an option

  • Save TomAuger/5c6c8942a8a7c333ab7095523ac085ad to your computer and use it in GitHub Desktop.

Select an option

Save TomAuger/5c6c8942a8a7c333ab7095523ac085ad to your computer and use it in GitHub Desktop.
Logic Pro X MIDI Script - Spitfire Audio Solo Violin - Map AfterTouch to Vibrato
/**
* "pitch bend to vibrato" for Spitfire Solo Strings
* Author: Tom Auger, based on work by Ian McDonald
* license: TDB
* version 0.1
*
* This script (for LogicPro X only) is a MIDI Script designed to make the Spitfire Solo Violin (Virtuoso mode) even more expressive
* by letting AfterTouch power the vibrato amount. No AT = no vibrator. More AT moves through the 2 vibrator layers.
* To get Tremolo, use Pitchbend Down
*
* Important - you need to map your Vibrato control in Kontakt to some CC. I don't know how to do it programatically
* Just open Kontakt, right-click the Vibrato Slider, choose "Learn MIDI CC Automation", and twiddle a CC knob or slider on your control surface
* Then, right-click again to see what CC# you've mapped it to, and set that number in the Script Parameters under "Vibrato CC Knob Mapping"
*/
// -- cc config
var VIB_CC = 21 // 21 for default vibrato
var VIB_MAX = 127; // Guessing here...
var VIB_MIN = 31; // Another educated guess...
var SUS_CC = 64;
var MOD_CC = 1;
var MOD_MAX = 127;
var EXPR_INPUT_CC = 11
// Parameters that can be changed by the UI
var p_ATMin = 0;
var p_ATMax = 127;
var p_VibCC = VIB_CC;
var p_ATDynFactor = 0;
const PB_MAX = 8192;
var vibratoEnabled = true; // disabled for Pitch bend / tremolo
var lastModValue = 0;
// logic api : https://manuals.info.apple.com/MANUALS/1000/MA1651/en_US/logic_pro_x_effects.pdf pg 185
// https://gist.github.com/djtech42/94e3b0980c684680ac79
// -- ui config
var PluginParameters = [
/* 0 */
{
name: 'AfterTouch Dead Zone (Min)',
type: 'lin',
minValue: 0, maxValue: 50,
defaultValue: 20,
numberOfSteps: 50
},
/* 1 */
{
name: 'AfterTouch Sensitivity (Max)',
type: 'lin',
minValue: 50, maxValue: 127,
defaultValue: 100,
numberOfSteps: 127 - 50
},
/* 2 */
{
name: 'Vibrato CC Knob Mapping',
type: 'lin',
minValue: 14, maxValue: 31,
defaultValue: VIB_CC,
numberOfSteps: 31 - 14
},
/* 3 */
{
name: 'AfterTouch Dynamics Bleed',
type: 'lin',
minValue: 0, maxValue: 64,
defaultValue: 0,
numberOfSteps: 64
},
];
function ParameterChanged(param, value) {
//Trace(`Param change detected. Param: ${param}, Value: ${value}`)
switch (param) {
case 0: // AfterTouch Min
p_ATMin = value;
break;
case 1: // AfterTouch Max
p_ATMax = value;
break;
case 2: // Vib CC
p_VibCC = Math.floor(value);
case 3: // AT Dynamics
p_ATDynFactor = value;
default:
break;
}
}
// ---
function Reset() {
setVibrato(0.0);
Trace('RESET\n');
}
Reset();
function HandleMIDI(e) {
if (e instanceof PitchBend) {
let pbValue = e.value;
//let pbFactor = pbValue / PB_MAX;
if (pbValue < 0) {
// Handle PB down
setVibratoEnabled(false);
} else {
setVibratoEnabled(true);
}
//Trace(`Pitch bend ${pbFactor * 100}%`);
return // <-- return without propagating the original pitch bend
} else if (e instanceof ChannelPressure) {
let atValue = e.value;
let atFactor = Math.min(Math.max(atValue - p_ATMin, 0) / (p_ATMax - p_ATMin), 1.0);
//Trace(`AfterTouch ${atFactor * 100}% (${atValue})`);
setVibrato(atFactor);
// Also, possibly modify dynamics slightly based on AT
if (p_ATDynFactor > 0){
setDynamics(atFactor);
}
//} else if (e instanceof NoteOn || e instanceof NoteOff) {
//} else if (e instanceof ControlChange) {
} else if (e.number == MOD_CC) {
lastModValue = e.value;
}
e.send();
}
function setVibrato(vibFactor) {
if (vibratoEnabled) {
let cc = new ControlChange();
cc.number = p_VibCC;
cc.value = Math.round(vibFactor * (VIB_MAX - VIB_MIN)) + VIB_MIN;
cc.send();
}
}
function setVibratoEnabled(toggle = true) {
vibratoEnabled = toggle;
let cc = new ControlChange();
cc.number = p_VibCC;
if (!vibratoEnabled) {
cc.value = 0;
} else {
cc.value = VIB_MIN;
}
cc.send();
}
function setDynamics(dynFactor){
let cc = new ControlChange();
cc.number = MOD_CC;
cc.value = lastModValue + (dynFactor * p_ATDynFactor);
cc.send();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment