Created
November 19, 2025 19:59
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * "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