🇫🇷 Français
Bonjour Ă tous đź‘‹
Je partage un script Logic Pro Scripter que j’ai développé et testé avec succès sur un Yamaha P-525, dans Logic Pro 11 sous macOS avec Pianoteq.
👉 Ce script (version 1.11) optimise :
• la vélocité des NoteOn grâce au contrôleur CC19 (Key Acceleration) spécifique Yamaha,
• la vélocité des NoteOff via la pente du Poly Aftertouch (18..32).
🎹 Important : sur le P-525, le CC19 est envoyé de manière polyphonique → une valeur CC19 est transmise juste avant chaque NoteOn, et donc associée individuellement à chaque touche. C’est cette donnée qui permet au script d’adapter dynamiquement la vélocité de chaque note.
Résultat :
• des courbes de vélocité plus réalistes, notamment lors des frappes lentes et de l’échappement,
• un meilleur réalisme dans Pianoteq, qui permet d’ajuster indépendamment les courbes NoteOn et NoteOff (0–127).
Paramètres ajustables :
• Ratio appliqué quand CC19=127 (par défaut ÷2)
• Ratio appliqué quand CC19=0 (par défaut ÷4)
• Facteur exponentiel pour contrôler la rapidité de l’effondrement vers 1 (par défaut 4)
• Vélocité NoteOff min/max, plage de pente, vélocité par défaut
👉 Le script fonctionne parfaitement avec Pianoteq : il suffit de régler dans Pianoteq les courbes de vélocité NoteOn et NoteOff (0–127) selon vos préférences.
⸻
🇬🇧 English
Hi everyone đź‘‹
Here’s a Logic Pro Scripter script I built and tested successfully with a Yamaha P-525, running on Logic Pro 11 (macOS) and Pianoteq.
👉 This script (version 1.11) optimizes:
• NoteOn velocity using Yamaha’s specific CC19 (Key Acceleration) controller,
• NoteOff velocity using the slope of Poly Aftertouch values (18..32).
🎹 Important: on the P-525, CC19 is transmitted polyphonically → a CC19 value is sent right before each NoteOn, and is therefore tied to each note individually. This allows the script to adapt velocity dynamically per key.
Result:
• more realistic velocity curves, especially for slow key strikes and escapement,
• better realism in Pianoteq, since NoteOn/NoteOff curves can be fine-tuned independently (0–127).
Adjustable parameters:
• Ratio when CC19=127 (default ÷2)
• Ratio when CC19=0 (default ÷4)
• Exponent factor to control how fast correction collapses to 1 (default 4)
• NoteOff velocity min/max, slope range, default fallback velocity
👉 Works great with Pianoteq: just adjust the NoteOn and NoteOff velocity curves (0–127) in Pianoteq to match your taste.
⸻
🎹 Script Logic Pro Scripter — Version 1.11
/*
Logic Pro Scripter — Yamaha P-525 NoteOn/NoteOff Optimizer v1.11
---------------------------------------------------------------
🇫🇷
- NoteOn : correction de la vélocité via CC19 (Key Acceleration Yamaha)
CC19=127 -> vélocité divisée par Ratio_127 (param, défaut ÷2)
CC19=1 -> pas de correction (ratio = 1)
127>CC19>1 -> interpolation exponentielle entre Ratio_127 et 1
CC19=0 -> vélocité divisée par Ratio_0 (param, défaut ÷4)
Le paramètre "Exponent Factor" contrôle la rapidité de l’effondrement.
- NoteOff : correction de la vélocité via la pente du dernier PolyAT (18..32).
- Garantit que la vélocité max (127) reste atteignable.
🇬🇧
- NoteOn: velocity correction via CC19 (Yamaha Key Acceleration)
CC19=127 -> velocity divided by Ratio_127 (param, default Ă·2)
CC19=1 -> no correction (ratio = 1)
127>CC19>1 -> exponential interpolation between Ratio_127 and 1
CC19=0 -> velocity divided by Ratio_0 (param, default Ă·4)
"Exponent Factor" controls how fast correction collapses to 1.
- NoteOff: velocity correction via slope of last PolyAT (18..32).
- Ensures max velocity (127) can still be reached.
*/
var PluginParameters = [
{ name:"Ratio at CC19=127 (slow key)", type:"lin", minValue:1, maxValue:8, numberOfSteps:7, defaultValue:2 },
{ name:"Ratio at CC19=0 (escapement)", type:"lin", minValue:1, maxValue:8, numberOfSteps:7, defaultValue:4 },
{ name:"Exponent Factor", type:"lin", minValue:0.5, maxValue:5, numberOfSteps:45, defaultValue:4 },
{ name:"OffVel Min", type:"lin", minValue:1, maxValue:127, numberOfSteps:126, defaultValue:1 },
{ name:"OffVel Max", type:"lin", minValue:1, maxValue:127, numberOfSteps:126, defaultValue:50 },
{ name:"Slope Min (x1000)", type:"lin", minValue:1, maxValue:200, numberOfSteps:199, defaultValue:10 },
{ name:"Slope Max (x1000)", type:"lin", minValue:5, maxValue:400, numberOfSteps:395, defaultValue:80 },
{ name:"Default OffVel", type:"lin", minValue:1, maxValue:127, numberOfSteps:126, defaultValue:100 },
{ name:"Pass-thru PolyAT", type:"menu", valueStrings:["No","Yes"], defaultValue:0 }
];
var paramValues = [];
for (var i=0;i<PluginParameters.length;i++){ paramValues[i] = PluginParameters[i].defaultValue; }
function ParameterChanged(index, value){ paramValues[index] = value; }
// --- Helpers ---
var lastCC19 = {};
var noteState = {};
function keyOf(e){ return (e.channel<<7)+e.pitch; }
function nowMs(){ return Date.now(); }
function withinWindowAT(v){ return v>=18 && v<=32; }
// --- PolyAT slope ---
function rememberATSample(e){
var k=keyOf(e), s=noteState[k]; if(!s){s={};noteState[k]=s;}
var t=nowMs(), v=e.value|0;
if(!withinWindowAT(v)) return false;
s.v2=v; s.t2=t; return true;
}
function computeSlopePerMs(s, noteOffTimeMs){
if(s&&s.v2!==undefined&&s.t2!==undefined){
var dv=Math.abs(s.v2), dt=(noteOffTimeMs-s.t2);
if(dt>0) return dv/dt;
}
return null;
}
function mapSlopeToVelocity(slope){
var vmin=getParam("OffVel Min")|0, vmax=getParam("OffVel Max")|0;
var sMin=(getParam("Slope Min (x1000)")|0)/1000.0;
var sMax=(getParam("Slope Max (x1000)")|0)/1000.0;
if(slope==null) return getParam("Default OffVel")|0;
var denom=(sMax-sMin); if(denom<=0) denom=1e-9;
var t=(slope-sMin)/denom; if(t<0) t=0; if(t>1) t=1;
var out=Math.round(vmin+t*(vmax-vmin));
if(out<1) out=1; if(out>127) out=127;
return out;
}
function getParam(name){ for(var i=0;i<PluginParameters.length;i++){ if(PluginParameters[i].name===name) return paramValues[i]; } return null; }
// --- Main ---
function HandleMIDI(e){
if(e instanceof ControlChange && e.number===19){ lastCC19[e.channel]=e.value; return; }
if(e instanceof NoteOn){
var vIn=e.velocity, cc=lastCC19[e.channel], ratio=1.0;
if(cc!==undefined){
var r127=paramValues[0], r0=paramValues[1], expF=paramValues[2];
if(cc===0){ ratio=1.0/r0; }
else if(cc===127){ ratio=1.0/r127; }
else if(cc===1){ ratio=1.0; }
else{
var t=(cc-1)/(127-1);
var curve=Math.pow(t,expF);
var rr1=1.0, rr127=1.0/r127;
ratio=rr1+curve*(rr127-rr1);
}
}
var vOut=Math.round(vIn*ratio);
if(vOut<1) vOut=1;
if(vOut>127) vOut=127;
if(vIn===127 && ratio<1.0) vOut=127; // ensure max reachable
e.velocity=vOut;
noteState[keyOf(e)]={};
e.send(); return;
}
if(e instanceof PolyPressure){ rememberATSample(e); if(getParam("Pass-thru PolyAT")===1){ e.send(); } return; }
if(e instanceof NoteOff){
var k=keyOf(e), s=noteState[k];
var slope=computeSlopePerMs(s,nowMs()), vel=mapSlopeToVelocity(slope);
e.velocity=vel; e.send(); delete noteState[k]; return;
}
e.send();
}
